├── 04-testing ├── e1 │ └── README.md ├── e2 │ └── README.md ├── benchmarks │ ├── fib.go │ └── fib_test.go ├── example_test.go └── stringutils │ ├── stringutils.go │ └── stringutils_test.go ├── 01-getting-started ├── e2 │ └── main.go └── e1 │ └── main.go ├── 06-http ├── e3 │ ├── main.go │ └── handler.go ├── e4 │ ├── main.go │ ├── handler.go │ └── handler_test.go ├── e2 │ └── main.go ├── e1 │ └── main.go ├── context │ ├── none │ │ └── main.go │ └── request │ │ └── main.go └── custom-servux-example │ └── main.go ├── go.mod ├── .gitignore ├── 08-concurrency ├── e0-sequential │ └── main.go ├── e1-synchronized │ └── main.go ├── e2-worker-pool │ └── main.go ├── e3-semaphone │ └── main.go └── e4-semaphore-with-timeout │ └── main.go ├── 05-interfaces ├── stringutils │ └── stringutils.go ├── main.go └── main_test.go ├── 03-command-line-tools ├── e3 │ ├── proverb.go │ └── main.go ├── e1 │ └── main.go └── e2 │ └── main.go ├── 02-language-basics ├── e3 │ ├── main.go │ └── proverbs.txt ├── e1 │ └── main.go └── e2 │ └── main.go ├── go.sum ├── README.md └── 07-dependencies └── main.go /04-testing/e1/README.md: -------------------------------------------------------------------------------- 1 | See ./stringutils/stringutils_test.go#TestUpper -------------------------------------------------------------------------------- /04-testing/e2/README.md: -------------------------------------------------------------------------------- 1 | See ./stringutils/stringutils_test.go#TestLower -------------------------------------------------------------------------------- /04-testing/benchmarks/fib.go: -------------------------------------------------------------------------------- 1 | package fibonacci 2 | 3 | func fib(n int) int { 4 | if n < 2 { 5 | return n 6 | } 7 | return fib(n-1) + fib(n-2) 8 | } 9 | -------------------------------------------------------------------------------- /04-testing/example_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "testing" 4 | 5 | func TestTruth(t *testing.T) { 6 | if true != true { 7 | t.Error("expected true to be true") 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /01-getting-started/e2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | u "github.com/jboursiquot/stringutils" 7 | ) 8 | 9 | func main() { 10 | fmt.Println(u.Upper("Hello, World")) 11 | fmt.Println(u.Lower("Hello, World")) 12 | } 13 | -------------------------------------------------------------------------------- /06-http/e3/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | ) 7 | 8 | func main() { 9 | ph := newProverbsHandler() 10 | 11 | mux := http.NewServeMux() 12 | mux.Handle("/proverbs/", ph) 13 | 14 | log.Fatal(http.ListenAndServe("127.0.0.1:8080", mux)) 15 | } 16 | -------------------------------------------------------------------------------- /06-http/e4/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | ) 7 | 8 | func main() { 9 | ph := newProverbsHandler() 10 | 11 | mux := http.NewServeMux() 12 | mux.Handle("/proverbs/", ph) 13 | 14 | log.Fatal(http.ListenAndServe("127.0.0.1:8080", mux)) 15 | } 16 | -------------------------------------------------------------------------------- /06-http/e2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | ) 8 | 9 | func main() { 10 | h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 11 | fmt.Fprintf(w, "Greetings!") 12 | }) 13 | 14 | log.Fatal(http.ListenAndServe("127.0.0.1:8080", h)) 15 | } 16 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/jboursiquot/go-next-steps 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/gorilla/mux v1.7.4 7 | github.com/jboursiquot/go-proverbs v0.0.2 8 | github.com/jboursiquot/stringutils v0.0.0-20200521133053-e946c1138532 9 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a 10 | ) 11 | -------------------------------------------------------------------------------- /06-http/e1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | ) 7 | 8 | type greeter struct{} 9 | 10 | func (h *greeter) ServeHTTP(w http.ResponseWriter, r *http.Request) { 11 | fmt.Fprintf(w, "Greetings!") 12 | } 13 | 14 | func main() { 15 | http.ListenAndServe("127.0.0.1:8080", &greeter{}) 16 | } 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | -------------------------------------------------------------------------------- /04-testing/benchmarks/fib_test.go: -------------------------------------------------------------------------------- 1 | package fibonacci 2 | 3 | import "testing" 4 | 5 | func benchFib(i int, b *testing.B) { 6 | for n := 0; n < b.N; n++ { 7 | fib(i) 8 | } 9 | } 10 | 11 | func BenchmarkFib1(b *testing.B) { benchFib(1, b) } 12 | func BenchmarkFib10(b *testing.B) { benchFib(10, b) } 13 | func BenchmarkFib20(b *testing.B) { benchFib(20, b) } 14 | -------------------------------------------------------------------------------- /04-testing/stringutils/stringutils.go: -------------------------------------------------------------------------------- 1 | package stringutils 2 | 3 | import "strings" 4 | 5 | // Upper returns the uppercase of the given string argument. 6 | func Upper(s string) string { 7 | return strings.ToUpper(s) 8 | } 9 | 10 | // Lower returns the lowercase of the given string argument. 11 | func Lower(s string) string { 12 | return strings.ToLower(s) 13 | } 14 | -------------------------------------------------------------------------------- /08-concurrency/e0-sequential/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net" 7 | ) 8 | 9 | func main() { 10 | for i := 5300; i <= 5500; i++ { 11 | conn, err := net.Dial("tcp", fmt.Sprintf("localhost:%d", i)) 12 | if err != nil { 13 | log.Printf("%d CLOSED (%s)\n", i, err) 14 | continue 15 | } 16 | conn.Close() 17 | log.Printf("%d OPEN\n", i) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /05-interfaces/stringutils/stringutils.go: -------------------------------------------------------------------------------- 1 | package stringutils 2 | 3 | import "strings" 4 | 5 | // Caser handles uppercasing and lowercasing strings. 6 | type Caser struct{} 7 | 8 | // Upper returns uppercased string. 9 | func (Caser) Upper(s string) string { 10 | return strings.ToUpper(s) 11 | } 12 | 13 | // Lower returns lowercase string. 14 | func (Caser) Lower(s string) string { 15 | return strings.ToLower(s) 16 | } 17 | -------------------------------------------------------------------------------- /03-command-line-tools/e3/proverb.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type proverb struct { 4 | line string 5 | chars map[rune]int 6 | } 7 | 8 | func (p *proverb) countChars() { 9 | if p.chars != nil { 10 | return 11 | } 12 | 13 | m := make(map[rune]int, 0) 14 | for _, c := range p.line { 15 | m[c] = m[c] + 1 16 | } 17 | p.chars = m 18 | } 19 | 20 | func newProverb(line string) *proverb { 21 | p := new(proverb) 22 | p.line = line 23 | p.countChars() 24 | return p 25 | } 26 | -------------------------------------------------------------------------------- /05-interfaces/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/jboursiquot/go-next-steps/05-interfaces/stringutils" 7 | ) 8 | 9 | type uppercaser interface { 10 | Upper(string) string 11 | } 12 | 13 | type lowercaser interface { 14 | Lower(string) string 15 | } 16 | 17 | func main() { 18 | c := stringutils.Caser{} 19 | fmt.Println(upcase(c, "hello")) 20 | fmt.Println(lowcase(c, "hello")) 21 | } 22 | 23 | func upcase(u uppercaser, str string) string { 24 | return u.Upper(str) 25 | } 26 | 27 | func lowcase(l lowercaser, str string) string { 28 | return l.Lower(str) 29 | } 30 | -------------------------------------------------------------------------------- /01-getting-started/e1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/jboursiquot/go-proverbs" 7 | ) 8 | 9 | const location = "Remote" 10 | 11 | var name string 12 | 13 | func main() { 14 | name = "Johnny" 15 | from := `Haiti` 16 | var n int = 2 17 | 18 | var proverb = "Undefined" 19 | if p, err := proverbs.Nth(4); err == nil { 20 | proverb = p.Saying 21 | } 22 | 23 | fmt.Printf("Hello, fellow %s Gophers!\n", location) 24 | fmt.Printf("My name is %s and I'm from %s.\n", name, from) 25 | fmt.Printf("By the time %d o'clock EST comes around, we'll know how to code in Go!\\n", n) 26 | fmt.Printf("Here's a Go proverb: %s\n", proverb) 27 | fmt.Println("Let's get started!") 28 | } 29 | -------------------------------------------------------------------------------- /02-language-basics/e3/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "strings" 7 | ) 8 | 9 | func main() { 10 | bs, err := ioutil.ReadFile("proverbs.txt") 11 | if err != nil { 12 | panic(fmt.Errorf("failed to read file: %s", err)) 13 | } 14 | proverbs := string(bs) 15 | 16 | lines := strings.Split(proverbs, "\n") 17 | for _, l := range lines { 18 | fmt.Printf("%s\n", l) 19 | for k, v := range charCount(l) { 20 | fmt.Printf("'%s'=%d, ", k, v) 21 | } 22 | fmt.Print("\n\n") 23 | } 24 | } 25 | 26 | func charCount(line string) map[string]int { 27 | m := make(map[string]int, 0) 28 | for _, char := range line { 29 | m[string(char)] = m[string(char)] + 1 30 | } 31 | return m 32 | } 33 | -------------------------------------------------------------------------------- /06-http/context/none/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | "time" 8 | ) 9 | 10 | func greetHandler(w http.ResponseWriter, r *http.Request) { 11 | log.Println("Handling greeting request") 12 | defer log.Println("Handled greeting request") 13 | 14 | completeAfter := time.After(5 * time.Second) 15 | 16 | for { 17 | select { 18 | case <-completeAfter: 19 | fmt.Fprintln(w, "Hello Gopher!") 20 | return 21 | default: 22 | time.Sleep(1 * time.Second) 23 | log.Println("Greetings are hard. Thinking...") 24 | } 25 | } 26 | } 27 | 28 | func main() { 29 | http.HandleFunc("/", greetHandler) 30 | log.Println("Starting server...") 31 | log.Fatal(http.ListenAndServe("127.0.0.1:8080", nil)) 32 | } 33 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc= 2 | github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= 3 | github.com/jboursiquot/go-proverbs v0.0.2 h1:LlJpbPfW6+HThOwZR+VsJwpRQL6xHOLsWjXJSZV2N40= 4 | github.com/jboursiquot/go-proverbs v0.0.2/go.mod h1:Fkb7X/GdAApOZjs+v4eREbG/UnzgHiLOLq7RbkKUf3U= 5 | github.com/jboursiquot/stringutils v0.0.0-20200521133053-e946c1138532 h1:/qihHi1/+jIKOrevypuZjeyjUbSYY5Y1Dl59gCk89zo= 6 | github.com/jboursiquot/stringutils v0.0.0-20200521133053-e946c1138532/go.mod h1:uNnl6RENFI2saDKeZqsWG3HHUh7nz2DOGwc7KeXr7zw= 7 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o= 8 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 9 | -------------------------------------------------------------------------------- /02-language-basics/e3/proverbs.txt: -------------------------------------------------------------------------------- 1 | Don't communicate by sharing memory, share memory by communicating. 2 | Concurrency is not parallelism. 3 | Channels orchestrate; mutexes serialize. 4 | The bigger the interface, the weaker the abstraction. 5 | Make the zero value useful. 6 | interface{} says nothing. 7 | Gofmt's style is no one's favorite, yet gofmt is everyone's favorite. 8 | A little copying is better than a little dependency. 9 | Syscall must always be guarded with build tags. 10 | Cgo must always be guarded with build tags. 11 | Cgo is not Go. 12 | With the unsafe package there are no guarantees. 13 | Clear is better than clever. 14 | Reflection is never clear. 15 | Errors are values. 16 | Don't just check errors, handle them gracefully. 17 | Design the architecture, name the components, document the details. 18 | Documentation is for users. 19 | Don't panic. -------------------------------------------------------------------------------- /03-command-line-tools/e1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "os" 7 | "strings" 8 | ) 9 | 10 | func main() { 11 | if len(os.Args) != 2 { 12 | fmt.Println("You must specify one argument as the file path.") 13 | os.Exit(1) 14 | } 15 | 16 | path := os.Args[1] 17 | bs, err := ioutil.ReadFile(path) 18 | if err != nil { 19 | fmt.Printf("Failed to read file: %s", err) 20 | os.Exit(1) 21 | } 22 | proverbs := string(bs) 23 | 24 | lines := strings.Split(proverbs, "\n") 25 | for _, l := range lines { 26 | fmt.Printf("%s\n", l) 27 | for k, v := range charCount(l) { 28 | fmt.Printf("'%c'=%d, ", k, v) 29 | } 30 | fmt.Print("\n\n") 31 | } 32 | } 33 | 34 | func charCount(line string) map[rune]int { // notice anything different here from previous exercises? 35 | m := make(map[rune]int, 0) 36 | for _, c := range line { 37 | m[c] = m[c] + 1 38 | } 39 | return m 40 | } 41 | -------------------------------------------------------------------------------- /06-http/context/request/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | "time" 8 | ) 9 | 10 | func greetHandler(w http.ResponseWriter, r *http.Request) { 11 | log.Println("Handling greeting request") 12 | defer log.Println("Handled greeting request") 13 | 14 | completeAfter := time.After(5 * time.Second) 15 | ctx := r.Context() 16 | 17 | for { 18 | select { 19 | case <-completeAfter: 20 | fmt.Fprintln(w, "Hello Gopher!") 21 | return 22 | case <-ctx.Done(): 23 | err := ctx.Err() 24 | log.Printf("Context Error: %s", err.Error()) 25 | http.Error(w, err.Error(), http.StatusRequestTimeout) 26 | return 27 | default: 28 | time.Sleep(1 * time.Second) 29 | log.Println("Greetings are hard. Thinking...") 30 | } 31 | } 32 | } 33 | 34 | func main() { 35 | http.HandleFunc("/", greetHandler) 36 | log.Println("Starting server...") 37 | log.Fatal(http.ListenAndServe("127.0.0.1:8080", nil)) 38 | } 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go Next Steps 2 | 3 | Writing effective and idiomatic production-grade Go 4 | 5 | :information_source: _Go Next Steps: Writing effective and idiomatic production-grade Go_ is a [live online training](https://learning.oreilly.com/live-training/courses/go-next-steps/0636920386711/#schedule) offered through O'Reilly Learning. 6 | 7 | Live trainings coming up: 8 | 9 | - May 21, 2020 https://learning.oreilly.com/live-training/courses/go-next-steps/0636920386711 10 | - July 16, 2020 https://learning.oreilly.com/live-training/courses/go-next-steps/0636920433330/ 11 | 12 | :exclamation: This training material was designed for a guided walkthough by the instructor and is best consumed along with the video recording of the training. That said, you can still use it to learn and review concepts as you see fit. 13 | 14 | Students, head over to the [Wiki](https://github.com/jboursiquot/go-next-steps/wiki) for the guided learning experience with your instructor. -------------------------------------------------------------------------------- /06-http/custom-servux-example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | ) 8 | 9 | type morningHandler struct{} 10 | 11 | func (mh *morningHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 12 | fmt.Fprintf(w, "Good Morning!") 13 | } 14 | 15 | type eveningHandler struct{} 16 | 17 | func (mh *eveningHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 18 | switch r.URL.Path { 19 | case "/evening/tea": 20 | w.WriteHeader(http.StatusTeapot) 21 | fmt.Fprintf(w, "Good Evening! Have some tea.") 22 | return 23 | case "/evening": 24 | fmt.Fprintf(w, "Good Evening!") 25 | return 26 | } 27 | } 28 | 29 | func main() { 30 | eh := &eveningHandler{} 31 | mh := &morningHandler{} 32 | 33 | mux := http.NewServeMux() 34 | mux.Handle("/evening/tea", eh) 35 | mux.Handle("/evening", eh) 36 | mux.Handle("/morning", mh) 37 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 38 | fmt.Fprintf(w, "Greetings!") 39 | }) 40 | 41 | log.Fatal(http.ListenAndServe("127.0.0.1:8080", mux)) 42 | } 43 | -------------------------------------------------------------------------------- /04-testing/stringutils/stringutils_test.go: -------------------------------------------------------------------------------- 1 | package stringutils_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/jboursiquot/go-next-steps/04-testing/stringutils" 7 | ) 8 | 9 | func TestUpper(t *testing.T) { 10 | input := "hello" 11 | want := "HELLO" 12 | got := stringutils.Upper(input) 13 | if want != got { 14 | t.Fatalf("wanted %s but got %s", want, got) 15 | } 16 | 17 | input = "café" 18 | want = "CAFÉ" 19 | got = stringutils.Upper(input) 20 | if want != got { 21 | t.Fatalf("wanted %s but got %s", want, got) 22 | } 23 | } 24 | 25 | // TestLower uses table-driven style of testing. 26 | func TestLower(t *testing.T) { 27 | tests := map[string]struct { 28 | input string 29 | want string 30 | }{ 31 | "basic": {input: "HELLO", want: "hello"}, 32 | "accented": {input: "CAFÉ", want: "café"}, 33 | } 34 | 35 | for name, tc := range tests { 36 | t.Run(name, func(t *testing.T) { 37 | got := stringutils.Lower(tc.input) 38 | if tc.want != got { 39 | t.Fatalf("wanted %s but got %s", tc.want, got) 40 | } 41 | }) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /02-language-basics/e1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | const proverbs = `Don't communicate by sharing memory, share memory by communicating. 9 | Concurrency is not parallelism. 10 | Channels orchestrate; mutexes serialize. 11 | The bigger the interface, the weaker the abstraction. 12 | Make the zero value useful. 13 | interface{} says nothing. 14 | Gofmt's style is no one's favorite, yet gofmt is everyone's favorite. 15 | A little copying is better than a little dependency. 16 | Syscall must always be guarded with build tags. 17 | Cgo must always be guarded with build tags. 18 | Cgo is not Go. 19 | With the unsafe package there are no guarantees. 20 | Clear is better than clever. 21 | Reflection is never clear. 22 | Errors are values. 23 | Don't just check errors, handle them gracefully. 24 | Design the architecture, name the components, document the details. 25 | Documentation is for users. 26 | Don't panic.` 27 | 28 | func main() { 29 | lines := strings.Split(proverbs, "\n") 30 | for i, l := range lines { 31 | wc := len(strings.Fields(l)) 32 | fmt.Printf("%d. %s (WC: %d)\n", i, l, wc) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /03-command-line-tools/e2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | "strings" 9 | ) 10 | 11 | func main() { 12 | path := pathFromFlag() 13 | if path == "" { 14 | path = pathFromEnv() 15 | } 16 | 17 | if path == "" { 18 | fmt.Println("You must specify one the file path with -f or as FILE environment variable.") 19 | os.Exit(1) 20 | } 21 | 22 | bs, err := ioutil.ReadFile(path) 23 | if err != nil { 24 | fmt.Printf("Failed to read file: %s", err) 25 | os.Exit(1) 26 | } 27 | proverbs := string(bs) 28 | 29 | lines := strings.Split(proverbs, "\n") 30 | for _, l := range lines { 31 | fmt.Printf("%s\n", l) 32 | for k, v := range charCount(l) { 33 | fmt.Printf("'%c'=%d, ", k, v) 34 | } 35 | fmt.Print("\n\n") 36 | } 37 | } 38 | 39 | func pathFromFlag() string { 40 | path := flag.String("f", "", "file flag") 41 | flag.Parse() 42 | return *path 43 | } 44 | 45 | func pathFromEnv() string { 46 | return os.Getenv("FILE") 47 | } 48 | 49 | func charCount(line string) map[rune]int { 50 | m := make(map[rune]int, 0) 51 | for _, c := range line { 52 | m[c] = m[c] + 1 53 | } 54 | return m 55 | } 56 | -------------------------------------------------------------------------------- /08-concurrency/e1-synchronized/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "net" 8 | "strconv" 9 | "sync" 10 | ) 11 | 12 | var host string 13 | var fromPort string 14 | var toPort string 15 | 16 | func init() { 17 | flag.StringVar(&host, "host", "127.0.0.1", "Host to scan.") 18 | flag.StringVar(&fromPort, "from", "8080", "Port to start scanning from") 19 | flag.StringVar(&toPort, "to", "8090", "Port at which to stop scanning") 20 | } 21 | 22 | func main() { 23 | flag.Parse() 24 | 25 | fp, err := strconv.Atoi(fromPort) 26 | if err != nil { 27 | log.Fatalln("Invalid 'from' port") 28 | } 29 | 30 | tp, err := strconv.Atoi(toPort) 31 | if err != nil { 32 | log.Fatalln("Invalid 'to' port") 33 | } 34 | 35 | if fp > tp { 36 | log.Fatalln("Invalid values for 'from' and 'to' port") 37 | } 38 | 39 | var wg sync.WaitGroup 40 | wg.Add(tp - fp) 41 | for i := fp; i <= tp; i++ { 42 | go func(p int) { 43 | defer wg.Done() 44 | conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", host, p)) 45 | if err != nil { 46 | log.Printf("%d CLOSED (%s)\n", p, err) 47 | return 48 | } 49 | conn.Close() 50 | log.Printf("%d OPEN\n", p) 51 | }(i) 52 | } 53 | wg.Wait() 54 | } 55 | -------------------------------------------------------------------------------- /03-command-line-tools/e3/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | "strings" 9 | ) 10 | 11 | func main() { 12 | path := pathFromFlag() 13 | if path == "" { 14 | path = pathFromEnv() 15 | } 16 | 17 | if path == "" { 18 | fmt.Println("You must specify one the file path with -f or as FILE environment variable.") 19 | os.Exit(1) 20 | } 21 | 22 | proverbs, err := loadProverbs(path) 23 | if err != nil { 24 | fmt.Printf("Failed to load proverbs: %s", err) 25 | os.Exit(1) 26 | } 27 | 28 | for _, p := range proverbs { 29 | fmt.Printf("%s\n", p.line) 30 | for k, v := range p.chars { 31 | fmt.Printf("'%c'=%d, ", k, v) 32 | } 33 | fmt.Print("\n\n") 34 | } 35 | } 36 | 37 | func pathFromFlag() string { 38 | path := flag.String("f", "", "file flag") 39 | flag.Parse() 40 | return *path 41 | } 42 | 43 | func pathFromEnv() string { 44 | return os.Getenv("FILE") 45 | } 46 | 47 | func loadProverbs(path string) ([]*proverb, error) { 48 | var proverbs []*proverb 49 | 50 | bs, err := ioutil.ReadFile(path) 51 | if err != nil { 52 | return nil, err 53 | } 54 | 55 | lines := strings.Split(string(bs), "\n") 56 | for _, line := range lines { 57 | proverbs = append(proverbs, newProverb(line)) 58 | } 59 | 60 | return proverbs, nil 61 | } 62 | -------------------------------------------------------------------------------- /02-language-basics/e2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | const proverbs = `Don't communicate by sharing memory, share memory by communicating. 9 | Concurrency is not parallelism. 10 | Channels orchestrate; mutexes serialize. 11 | The bigger the interface, the weaker the abstraction. 12 | Make the zero value useful. 13 | interface{} says nothing. 14 | Gofmt's style is no one's favorite, yet gofmt is everyone's favorite. 15 | A little copying is better than a little dependency. 16 | Syscall must always be guarded with build tags. 17 | Cgo must always be guarded with build tags. 18 | Cgo is not Go. 19 | With the unsafe package there are no guarantees. 20 | Clear is better than clever. 21 | Reflection is never clear. 22 | Errors are values. 23 | Don't just check errors, handle them gracefully. 24 | Design the architecture, name the components, document the details. 25 | Documentation is for users. 26 | Don't panic.` 27 | 28 | func main() { 29 | lines := strings.Split(proverbs, "\n") 30 | for _, l := range lines { 31 | fmt.Printf("%s\n", l) 32 | for k, v := range charCount(l) { 33 | fmt.Printf("'%s'=%d, ", k, v) 34 | } 35 | fmt.Print("\n\n") 36 | } 37 | } 38 | 39 | func charCount(line string) map[string]int { 40 | m := make(map[string]int, 0) 41 | for _, char := range line { 42 | m[string(char)] = m[string(char)] + 1 43 | } 44 | return m 45 | } 46 | -------------------------------------------------------------------------------- /05-interfaces/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | func TestUpper(t *testing.T) { 9 | tests := map[string]struct { 10 | input string 11 | want string 12 | uppercaser uppercaser 13 | }{ 14 | "basic": {input: "hello", want: "HELLO", uppercaser: mockCaser{}}, 15 | "accented": {input: "café", want: "CAFÉ", uppercaser: mockCaser{}}, 16 | } 17 | 18 | for name, tc := range tests { 19 | t.Run(name, func(t *testing.T) { 20 | got := upcase(tc.uppercaser, tc.input) 21 | if tc.want != got { 22 | t.Fatalf("wanted %s but got %s", tc.want, got) 23 | } 24 | }) 25 | } 26 | } 27 | 28 | func TestLower(t *testing.T) { 29 | tests := map[string]struct { 30 | input string 31 | want string 32 | lowercaser lowercaser 33 | }{ 34 | "basic": {input: "HELLO", want: "hello", lowercaser: mockCaser{}}, 35 | "accented": {input: "CAFÉ", want: "café", lowercaser: mockCaser{}}, 36 | } 37 | 38 | for name, tc := range tests { 39 | t.Run(name, func(t *testing.T) { 40 | got := lowcase(tc.lowercaser, tc.input) 41 | if tc.want != got { 42 | t.Fatalf("wanted %s but got %s", tc.want, got) 43 | } 44 | }) 45 | } 46 | } 47 | 48 | type mockCaser struct{} 49 | 50 | func (mockCaser) Upper(s string) string { 51 | return strings.ToUpper(s) 52 | } 53 | 54 | func (mockCaser) Lower(s string) string { 55 | return strings.ToLower(s) 56 | } 57 | -------------------------------------------------------------------------------- /06-http/e3/handler.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net/http" 7 | "strconv" 8 | ) 9 | 10 | type proverb struct { 11 | id int 12 | value string 13 | } 14 | 15 | type proverbsHandler struct { 16 | proverbs []proverb 17 | } 18 | 19 | func (ph *proverbsHandler) lookup(id int) (*proverb, error) { 20 | for _, p := range ph.proverbs { 21 | if id == p.id { 22 | return &p, nil 23 | } 24 | } 25 | return nil, errUnknownProverb 26 | } 27 | 28 | func newProverbsHandler() *proverbsHandler { 29 | return &proverbsHandler{ 30 | proverbs: []proverb{ 31 | {id: 1, value: "Don't communicate by sharing memory, share memory by communicating."}, 32 | {id: 2, value: "Concurrency is not parallelism."}, 33 | {id: 3, value: "Channels orchestrate; mutexes serialize."}, 34 | {id: 4, value: "The bigger the interface, the weaker the abstraction."}, 35 | {id: 5, value: "Make the zero value useful."}, 36 | }, 37 | } 38 | } 39 | 40 | func (ph *proverbsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 41 | switch r.Method { 42 | case http.MethodGet: 43 | id, err := strconv.Atoi(r.URL.Path[len("/proverbs/"):]) 44 | if err != nil { 45 | w.WriteHeader(http.StatusBadRequest) 46 | return 47 | } 48 | 49 | p, err := ph.lookup(id) 50 | if err == errUnknownProverb { 51 | http.Error(w, errUnknownProverb.Error(), http.StatusNotFound) 52 | return 53 | } 54 | 55 | fmt.Fprintln(w, p.value) 56 | return 57 | default: 58 | w.WriteHeader(http.StatusMethodNotAllowed) 59 | return 60 | } 61 | } 62 | 63 | var errUnknownProverb = errors.New("Unknown Proverb") 64 | -------------------------------------------------------------------------------- /06-http/e4/handler.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net/http" 7 | "strconv" 8 | ) 9 | 10 | type proverb struct { 11 | id int 12 | value string 13 | } 14 | 15 | type proverbsHandler struct { 16 | proverbs []proverb 17 | } 18 | 19 | func (ph *proverbsHandler) lookup(id int) (*proverb, error) { 20 | for _, p := range ph.proverbs { 21 | if id == p.id { 22 | return &p, nil 23 | } 24 | } 25 | return nil, errUnknownProverb 26 | } 27 | 28 | func newProverbsHandler() *proverbsHandler { 29 | return &proverbsHandler{ 30 | proverbs: []proverb{ 31 | {id: 1, value: "Don't communicate by sharing memory, share memory by communicating."}, 32 | {id: 2, value: "Concurrency is not parallelism."}, 33 | {id: 3, value: "Channels orchestrate; mutexes serialize."}, 34 | {id: 4, value: "The bigger the interface, the weaker the abstraction."}, 35 | {id: 5, value: "Make the zero value useful."}, 36 | }, 37 | } 38 | } 39 | 40 | func (ph *proverbsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 41 | switch r.Method { 42 | case http.MethodGet: 43 | id, err := strconv.Atoi(r.URL.Path[len("/proverbs/"):]) 44 | if err != nil { 45 | w.WriteHeader(http.StatusBadRequest) 46 | return 47 | } 48 | 49 | p, err := ph.lookup(id) 50 | if err == errUnknownProverb { 51 | http.Error(w, errUnknownProverb.Error(), http.StatusNotFound) 52 | return 53 | } 54 | 55 | fmt.Fprintln(w, p.value) 56 | return 57 | default: 58 | w.WriteHeader(http.StatusMethodNotAllowed) 59 | return 60 | } 61 | } 62 | 63 | var errUnknownProverb = errors.New("Unknown Proverb") 64 | -------------------------------------------------------------------------------- /06-http/e4/handler_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | "strings" 7 | "testing" 8 | ) 9 | 10 | func TestProverbsHandler(t *testing.T) { 11 | cases := []struct { 12 | scenario string 13 | method string 14 | endpoint string 15 | expectedStatusCode int 16 | expectedBody string 17 | }{ 18 | { 19 | scenario: "ReturnsSpecificProverb", 20 | method: http.MethodGet, 21 | endpoint: "/proverbs/3", 22 | expectedStatusCode: http.StatusOK, 23 | expectedBody: "Channels orchestrate; mutexes serialize.", 24 | }, 25 | { 26 | scenario: "ReturnsBadRequest", 27 | method: http.MethodGet, 28 | endpoint: "/proverbs/", 29 | expectedStatusCode: http.StatusBadRequest, 30 | expectedBody: "", 31 | }, 32 | { 33 | scenario: "ReturnsMethodNotAllowed", 34 | method: http.MethodPut, 35 | endpoint: "/proverbs/3", 36 | expectedStatusCode: http.StatusMethodNotAllowed, 37 | expectedBody: "", 38 | }, 39 | } 40 | 41 | for _, c := range cases { 42 | r, _ := http.NewRequest(c.method, c.endpoint, nil) 43 | w := httptest.NewRecorder() 44 | h := newProverbsHandler() 45 | h.ServeHTTP(w, r) 46 | 47 | if w.Code != c.expectedStatusCode { 48 | t.Fatalf("expected %d but got %d for status code", c.expectedStatusCode, w.Code) 49 | } 50 | 51 | body := strings.TrimSpace(w.Body.String()) 52 | if body != c.expectedBody { 53 | t.Fatalf("expected %s but got %s for body", c.expectedBody, body) 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /07-dependencies/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "net/http" 8 | "strconv" 9 | 10 | "github.com/gorilla/mux" 11 | ) 12 | 13 | func main() { 14 | ph := newProverbsHandler() 15 | 16 | r := mux.NewRouter() 17 | r.Handle("/proverbs/{id}", ph) 18 | 19 | log.Fatal(http.ListenAndServe("127.0.0.1:8080", r)) 20 | } 21 | 22 | type proverb struct { 23 | id int 24 | value string 25 | } 26 | 27 | type proverbsHandler struct { 28 | proverbs []proverb 29 | } 30 | 31 | func (ph *proverbsHandler) lookup(id int) (*proverb, error) { 32 | for _, p := range ph.proverbs { 33 | if id == p.id { 34 | return &p, nil 35 | } 36 | } 37 | return nil, errUnknownProverb 38 | } 39 | 40 | func newProverbsHandler() *proverbsHandler { 41 | return &proverbsHandler{ 42 | proverbs: []proverb{ 43 | proverb{id: 1, value: "Don't communicate by sharing memory, share memory by communicating."}, 44 | proverb{id: 2, value: "Concurrency is not parallelism."}, 45 | proverb{id: 3, value: "Channels orchestrate; mutexes serialize."}, 46 | proverb{id: 4, value: "The bigger the interface, the weaker the abstraction."}, 47 | proverb{id: 5, value: "Make the zero value useful."}, 48 | }, 49 | } 50 | } 51 | 52 | func (ph *proverbsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 53 | vars := mux.Vars(r) 54 | switch r.Method { 55 | case http.MethodGet: 56 | id, err := strconv.Atoi(vars["id"]) 57 | if err != nil { 58 | w.WriteHeader(http.StatusBadRequest) 59 | return 60 | } 61 | 62 | p, err := ph.lookup(id) 63 | if err == errUnknownProverb { 64 | http.Error(w, errUnknownProverb.Error(), http.StatusNotFound) 65 | return 66 | } 67 | 68 | fmt.Fprintln(w, p.value) 69 | return 70 | default: 71 | w.WriteHeader(http.StatusMethodNotAllowed) 72 | return 73 | } 74 | } 75 | 76 | var errUnknownProverb = errors.New("Unknown Proverb") 77 | -------------------------------------------------------------------------------- /08-concurrency/e2-worker-pool/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "flag" 6 | "fmt" 7 | "net" 8 | "os" 9 | "runtime" 10 | "sort" 11 | "strconv" 12 | "strings" 13 | ) 14 | 15 | var host string 16 | var ports string 17 | var numWorkers int 18 | 19 | func init() { 20 | flag.StringVar(&host, "host", "127.0.0.1", "Host to scan.") 21 | flag.StringVar(&ports, "ports", "80", "Port(s) (e.g. 80, 22-100).") 22 | flag.IntVar(&numWorkers, "workers", runtime.NumCPU(), "Number of workers. Defaults to 10.") 23 | } 24 | 25 | func main() { 26 | flag.Parse() 27 | 28 | portsToScan, err := parsePortsToScan(ports) 29 | if err != nil { 30 | fmt.Printf("Failed to parse ports to scan: %s", err) 31 | os.Exit(1) 32 | } 33 | 34 | portsChan := make(chan int, numWorkers) 35 | resultsChan := make(chan int) 36 | 37 | for i := 0; i < cap(portsChan); i++ { // numWorkers also acceptable here 38 | go worker(host, portsChan, resultsChan) 39 | } 40 | 41 | go func() { 42 | for _, p := range portsToScan { 43 | portsChan <- p 44 | } 45 | }() 46 | 47 | var openPorts []int 48 | for i := 0; i < len(portsToScan); i++ { 49 | if p := <-resultsChan; p != 0 { // non-zero port means it's open 50 | openPorts = append(openPorts, p) 51 | } 52 | } 53 | 54 | close(portsChan) 55 | close(resultsChan) 56 | 57 | fmt.Println("RESULTS") 58 | sort.Ints(openPorts) 59 | for _, p := range openPorts { 60 | fmt.Printf("%d - open\n", p) 61 | } 62 | } 63 | 64 | func parsePortsToScan(portsFlag string) ([]int, error) { 65 | p, err := strconv.Atoi(portsFlag) 66 | if err == nil { 67 | return []int{p}, nil 68 | } 69 | 70 | ports := strings.Split(portsFlag, "-") 71 | if len(ports) != 2 { 72 | return nil, errors.New("unable to determine port(s) to scan") 73 | } 74 | 75 | minPort, err := strconv.Atoi(ports[0]) 76 | if err != nil { 77 | return nil, fmt.Errorf("failed to convert %s to a valid port number", ports[0]) 78 | } 79 | 80 | maxPort, err := strconv.Atoi(ports[1]) 81 | if err != nil { 82 | return nil, fmt.Errorf("failed to convert %s to a valid port number", ports[1]) 83 | } 84 | 85 | if minPort <= 0 || maxPort <= 0 { 86 | return nil, fmt.Errorf("port numbers must be greater than 0") 87 | } 88 | 89 | var results []int 90 | for p := minPort; p <= maxPort; p++ { 91 | results = append(results, p) 92 | } 93 | return results, nil 94 | } 95 | 96 | func worker(host string, portsChan <-chan int, resultsChan chan<- int) { 97 | for p := range portsChan { 98 | address := fmt.Sprintf("%s:%d", host, p) 99 | conn, err := net.Dial("tcp", address) 100 | if err != nil { 101 | fmt.Printf("%d CLOSED (%s)\n", p, err) 102 | resultsChan <- 0 103 | continue 104 | } 105 | conn.Close() 106 | resultsChan <- p 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /08-concurrency/e3-semaphone/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "flag" 7 | "fmt" 8 | "net" 9 | "os" 10 | "runtime" 11 | "sort" 12 | "strconv" 13 | "strings" 14 | 15 | "golang.org/x/sync/semaphore" 16 | ) 17 | 18 | var host string 19 | var ports string 20 | var numWorkers int 21 | 22 | func init() { 23 | flag.StringVar(&host, "host", "127.0.0.1", "Host to scan.") 24 | flag.StringVar(&ports, "ports", "80", "Port(s) (e.g. 80, 22-100).") 25 | flag.IntVar(&numWorkers, "workers", runtime.NumCPU(), "Number of workers. Defaults to system's number of CPUs.") 26 | } 27 | 28 | func main() { 29 | flag.Parse() 30 | 31 | portsToScan, err := parsePortsToScan(ports) 32 | if err != nil { 33 | fmt.Printf("Failed to parse ports to scan: %s", err) 34 | os.Exit(1) 35 | } 36 | 37 | sem := semaphore.NewWeighted(int64(numWorkers)) 38 | openPorts := make([]int, 0) 39 | ctx := context.TODO() 40 | 41 | for _, port := range portsToScan { 42 | if err := sem.Acquire(ctx, 1); err != nil { 43 | fmt.Printf("Failed to acquire semaphore: %v", err) 44 | break 45 | } 46 | 47 | go func(port int) { 48 | defer sem.Release(1) 49 | p := scan(host, port) 50 | if p != 0 { 51 | openPorts = append(openPorts, p) 52 | } 53 | }(port) 54 | } 55 | 56 | // We block here until done. 57 | if err := sem.Acquire(ctx, int64(numWorkers)); err != nil { 58 | fmt.Printf("Failed to acquire semaphore: %v", err) 59 | } 60 | 61 | fmt.Println("RESULTS") 62 | sort.Ints(openPorts) 63 | for _, p := range openPorts { 64 | fmt.Printf("%d - open\n", p) 65 | } 66 | } 67 | 68 | func parsePortsToScan(portsFlag string) ([]int, error) { 69 | p, err := strconv.Atoi(portsFlag) 70 | if err == nil { 71 | return []int{p}, nil 72 | } 73 | 74 | ports := strings.Split(portsFlag, "-") 75 | if len(ports) != 2 { 76 | return nil, errors.New("unable to determine port(s) to scan") 77 | } 78 | 79 | minPort, err := strconv.Atoi(ports[0]) 80 | if err != nil { 81 | return nil, fmt.Errorf("failed to convert %s to a valid port number", ports[0]) 82 | } 83 | 84 | maxPort, err := strconv.Atoi(ports[1]) 85 | if err != nil { 86 | return nil, fmt.Errorf("failed to convert %s to a valid port number", ports[1]) 87 | } 88 | 89 | if minPort <= 0 || maxPort <= 0 { 90 | return nil, fmt.Errorf("port numbers must be greater than 0") 91 | } 92 | 93 | var results []int 94 | for p := minPort; p <= maxPort; p++ { 95 | results = append(results, p) 96 | } 97 | return results, nil 98 | } 99 | 100 | func scan(host string, port int) int { 101 | address := fmt.Sprintf("%s:%d", host, port) 102 | conn, err := net.Dial("tcp", address) 103 | if err != nil { 104 | fmt.Printf("%d CLOSED (%s)\n", port, err) 105 | return 0 106 | } 107 | conn.Close() 108 | return port 109 | } 110 | -------------------------------------------------------------------------------- /08-concurrency/e4-semaphore-with-timeout/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "flag" 7 | "fmt" 8 | "math/rand" 9 | "net" 10 | "os" 11 | "runtime" 12 | "sort" 13 | "strconv" 14 | "strings" 15 | "time" 16 | 17 | "golang.org/x/sync/semaphore" 18 | ) 19 | 20 | var host string 21 | var ports string 22 | var numWorkers int 23 | var timeout int 24 | 25 | func init() { 26 | flag.StringVar(&host, "host", "127.0.0.1", "Host to scan.") 27 | flag.StringVar(&ports, "ports", "80", "Port(s) (e.g. 80, 22-100).") 28 | flag.IntVar(&numWorkers, "workers", runtime.NumCPU(), "Number of workers. Defaults to system's number of CPUs.") 29 | flag.IntVar(&timeout, "timeout", 5, "Timeout in seconds (default is 5).") 30 | } 31 | 32 | func main() { 33 | flag.Parse() 34 | 35 | portsToScan, err := parsePortsToScan(ports) 36 | if err != nil { 37 | fmt.Printf("Failed to parse ports to scan: %s", err) 38 | os.Exit(1) 39 | } 40 | 41 | sem := semaphore.NewWeighted(int64(numWorkers)) 42 | openPorts := make([]int, 0) 43 | ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second) 44 | defer cancel() 45 | 46 | for _, port := range portsToScan { 47 | if err := sem.Acquire(ctx, 1); err != nil { 48 | fmt.Printf("Failed to acquire semaphore: %v", err) 49 | break 50 | } 51 | 52 | go func(port int) { 53 | defer sem.Release(1) 54 | sleepy(10) 55 | p := scan(host, port) 56 | if p != 0 { 57 | openPorts = append(openPorts, p) 58 | } 59 | }(port) 60 | } 61 | 62 | if err := sem.Acquire(ctx, int64(numWorkers)); err != nil { 63 | fmt.Printf("Failed to acquire semaphore: %v", err) 64 | } 65 | 66 | sort.Ints(openPorts) 67 | for _, p := range openPorts { 68 | fmt.Printf("%d - open\n", p) 69 | } 70 | } 71 | 72 | func parsePortsToScan(portsFlag string) ([]int, error) { 73 | p, err := strconv.Atoi(portsFlag) 74 | if err == nil { 75 | return []int{p}, nil 76 | } 77 | 78 | ports := strings.Split(portsFlag, "-") 79 | if len(ports) != 2 { 80 | return nil, errors.New("unable to determine port(s) to scan") 81 | } 82 | 83 | minPort, err := strconv.Atoi(ports[0]) 84 | if err != nil { 85 | return nil, fmt.Errorf("failed to convert %s to a valid port number", ports[0]) 86 | } 87 | 88 | maxPort, err := strconv.Atoi(ports[1]) 89 | if err != nil { 90 | return nil, fmt.Errorf("failed to convert %s to a valid port number", ports[1]) 91 | } 92 | 93 | if minPort <= 0 || maxPort <= 0 { 94 | return nil, fmt.Errorf("port numbers must be greater than 0") 95 | } 96 | 97 | var results []int 98 | for p := minPort; p <= maxPort; p++ { 99 | results = append(results, p) 100 | } 101 | return results, nil 102 | } 103 | 104 | func scan(host string, port int) int { 105 | address := fmt.Sprintf("%s:%d", host, port) 106 | conn, err := net.Dial("tcp", address) 107 | if err != nil { 108 | fmt.Printf("%d CLOSED (%s)\n", port, err) 109 | return 0 110 | } 111 | conn.Close() 112 | return port 113 | } 114 | 115 | func sleepy(max int) { 116 | rand.Seed(time.Now().UnixNano()) 117 | n := rand.Intn(max) 118 | time.Sleep(time.Duration(n) * time.Second) 119 | } 120 | --------------------------------------------------------------------------------