├── .gitignore ├── 00-docs ├── Modules.MD └── Recommendations-and-rules.MD ├── 01-intro ├── demoapp │ ├── cmd │ │ └── app │ │ │ └── main.go │ └── pkg │ │ └── stringutils │ │ ├── stringutils.go │ │ └── stringutils_test.go └── hello │ ├── hello │ └── main.go ├── 02-syntax ├── 1-basic │ ├── basic.go │ └── basic_test.go ├── 2-names │ └── pkg │ │ └── names │ │ ├── names.go │ │ └── names_test.go ├── 3-array_slice_map │ ├── array_slice_map.go │ └── array_slice_map_test.go ├── 4-pointers │ └── pointers.go ├── 4-struct │ └── struct.go ├── 5-methods_ifaces │ ├── methods_ifaces.go │ └── methods_ifaces_test.go ├── 6-errors │ └── errors.go └── 7-channels │ ├── channels.go │ └── channels_test.go ├── 03-algorithms ├── 1-search │ ├── search.go │ └── search_test.go ├── 2-recursion │ └── recursion.go ├── 3-dynamic │ └── dynamic.go └── 4-graph │ ├── graph.go │ └── graph_test.go ├── 04-datastructs ├── 0-arrays │ └── arrays.go ├── 1-list │ ├── list.go │ └── list_test.go └── 2-bst │ └── bst.go ├── 05-io ├── 1-net_io │ ├── net_io.go │ └── net_io_test.go ├── 2-writer-example │ ├── strings.txt │ └── writer.go ├── 3-bufio_example │ └── bufio.go ├── 4-files │ └── files.go ├── 5-input │ └── input.go ├── 6-flags │ └── flags.go └── 7-json_serialize │ └── json.go ├── 06-oop ├── 1-methods │ └── methods.go ├── 2-dip │ ├── ifaces.go │ └── ifaces_test.go ├── 3-embedding │ └── embedding.go ├── 4-constructor │ ├── constructor.go │ └── constructor_test.go └── 5-hw │ ├── hw.go │ └── hw_test.go ├── 07-testing ├── 1-simple │ ├── simple.go │ └── simple_test.go ├── 2-testmain │ ├── testmain.go │ └── testmain_test.go ├── 3-table │ ├── table.go │ └── table_test.go ├── 4-refactoring │ ├── refactoring.go │ └── refactoring_test.go ├── 5-db │ ├── db.go │ └── db_test.go ├── 6-handler │ ├── handler.go │ └── handler_test.go ├── 7-tdd │ ├── tdd.go │ └── tdd_test.go ├── 8-benchmarks │ ├── search.go │ └── search_test.go └── flags │ └── flag_test.go ├── 08-prof_debug ├── 1-bench_profile │ ├── search.go │ └── search_test.go ├── 2-app_profile │ └── app-profile.go ├── 3-debug │ └── two_sum.go └── 4-trace │ ├── trace_cpu │ └── trace.go │ └── trace_mem │ └── trace.go ├── 09-interfaces ├── 1-polymorph │ └── polymorph.go ├── 2-quiz │ └── quiz.go ├── 3-assertion │ └── assertion.go ├── 4-type_switch │ └── switch.go ├── 5-important │ └── important.go ├── 6-generics │ └── generics.go └── 7-DIP │ └── dip.go ├── 10-concurrency ├── 1-goroutine │ └── goroutine.go ├── 10-pattern-pipeline │ └── pipeline.go ├── 11-context │ └── context.go ├── 2-chan │ └── chan.go ├── 3-chan_select │ └── select.go ├── 4-waitgroup │ └── sync.go ├── 5-shared_mem │ └── shared.go ├── 6-common_errors │ └── errors.go ├── 7-atomic │ └── atomic.go ├── 8-pattern-fan-out │ └── fan-out.go └── 9-pattern-fan-in │ └── fan-in.go ├── 11-network ├── 1-daytime │ ├── client │ │ └── daytime-client.go │ └── server │ │ └── daytime-server.go ├── 2-echo │ └── echo.go ├── 3-deadline │ └── deadline.go └── 4-testing │ ├── timeserver.go │ └── timeserver_test.go ├── 12-web-apps ├── 1-http │ ├── client │ │ └── simple-client.go │ └── server │ │ └── simple-server.go ├── 2-custom_server │ └── custom-server.go ├── 3-gorilla_mux │ └── gorilla.go ├── 4-template │ └── template.go ├── 5-testing │ ├── testing.go │ └── testing_test.go └── hw │ └── main.go ├── 13-api ├── 1-api │ ├── api.go │ ├── api_test.go │ ├── middleware.go │ └── server.go └── 2-api │ ├── cmd │ └── server │ │ └── server.go │ └── pkg │ └── api │ ├── api.go │ ├── api_test.go │ ├── books.go │ ├── books_test.go │ ├── jwt.go │ ├── jwt_test.go │ ├── middleware.go │ ├── middleware_test.go │ ├── session.go │ └── session_test.go ├── 14-RPC ├── 1-Go-RPC │ ├── cmd │ │ ├── client │ │ │ └── rpc-client.go │ │ └── server │ │ │ └── rpc-server.go │ └── pkg │ │ └── books │ │ └── books.go └── 2-gRPC │ ├── books_proto │ ├── books.pb.go │ ├── books.proto │ └── books_grpc.pb.go │ ├── client │ └── grpc-client.go │ └── server │ └── grpc-server.go ├── 15-sql └── books_db │ ├── 1-schema.sql │ └── 2-data.sql ├── 16-db-apps ├── 1-database-sql │ └── database-sql.go ├── 2-pgx │ ├── pgx.go │ └── pgx_test.go ├── pkg │ ├── api │ │ └── api.go │ └── db │ │ ├── db.go │ │ ├── memsql │ │ └── memsql.go │ │ └── pgsql │ │ └── pgsql.go └── schema.sql ├── 17-system-design ├── SOLID │ ├── 1-SRP │ │ └── srp.go │ ├── 2-OCP │ │ ├── ocp.go │ │ ├── ocp_refactored.go │ │ ├── ocp_refactored_test.go │ │ └── ocp_test.go │ ├── 3-LSP │ │ └── lsp.go │ ├── 4-ISP │ │ └── isp.go │ └── 5-DIP │ │ └── dip.go └── app │ ├── cmd │ ├── appclient │ │ └── appclient.go │ └── appserver │ │ └── appserver.go │ └── internal │ ├── api │ └── api.go │ ├── db │ └── db.go │ ├── models │ └── models.go │ └── server │ └── server.go ├── 18-microservices └── microservice │ ├── build │ ├── Dockerfile │ ├── deployment.yaml │ └── service.yaml │ ├── cmd │ └── app │ │ └── main.go │ ├── go.mod │ ├── go.sum │ └── pkg │ └── api │ └── api.go ├── 19-queue ├── 1-kafka │ └── kafka.go └── docker-compose.yml ├── 20-NoSQL ├── 1-mongo │ └── mongo.go ├── 2-KVStore │ ├── cmd │ │ └── app │ │ │ └── app.go │ └── pkg │ │ └── kvdb │ │ └── kvdb.go ├── 3-redis │ └── redis.go └── docker-compose.yml ├── 21-interview ├── 01-basics.go ├── 01-basics_test.go ├── 02-channels.go └── 02-channels_test.go ├── GoSearch ├── cmd │ └── gosearch │ │ └── gosearh.go └── pkg │ └── crawler │ ├── crawler.go │ ├── membot │ └── membot.go │ └── spider │ ├── spider.go │ └── spider_test.go ├── README.md ├── file.txt ├── final.md ├── go.mod ├── go.sum └── lynks ├── memcache ├── cmd │ └── server │ │ └── main.go └── pkg │ └── redis │ └── redis.go └── shortener ├── cmd └── server │ └── main.go └── pkg └── urls └── urls.go /.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 | .vscode 15 | .idea -------------------------------------------------------------------------------- /00-docs/Modules.MD: -------------------------------------------------------------------------------- 1 | ## Модули ## 2 | 3 | Модули традиционно вызывают много вопросов у слушателей курса. Авторы Go написали серию статей по модулям: https://go.dev/blog/using-go-modules, однако в этой заметке я постараюсь привести базовую инструкцию по правильному использованию модулей во время обучения. 4 | 5 | Модуль - это проект. Для нас проектом является весь курс, поэтому в своих репозиториях мы должны создать только один модуль в корне каталога нашего репозитория, там же где и каталог `.git`. 6 | 7 | Ваша структура каталогов домашних заданий должна примерно повторять структуру этого репозитория. То есть требуется создать корневой каталог (например `go-core-4`), в нём создать модуль с помощью, например, `go mod init go-core-4`. Внутри этого каталога нужно создавать отдельные каталоги для каждого урока. 8 | Пример структуры каталогов: 9 | ``` 10 | ├───00-docs 11 | ├───01-intro 12 | │ ├───demoapp 13 | │ │ ├───cmd 14 | │ │ │ └───app 15 | │ │ └───pkg 16 | │ │ └───stringutils 17 | │ └───hello 18 | ├───02-syntax 19 | │ 20 | │ go.mod 21 | │ README.md 22 | ``` 23 | При создании модуля создаётся файл go.mod, в котором указано имя модуля, который мы ввели как аргумент команды `go mod init`. Например, `go.mod` этого репозитория начинается с: `module go-core-4`. 24 | 25 | Название модуля является псевдонимом корневого каталога проекта и именно с этого названия начинаются пути импорта пакетов, находящихся в каталогах внутри каталога модуля. После имени модуля для каждого пакета идёт структура каталогов для данного пакета. 26 | Например, в первом занятии у нас есть путь импорта: `"go-core-4/01-intro/demoapp/pkg/stringutils"`. Этот путь начинается с имени модуля, после чего идет путь до каталога с пакетом. То есть этот пакет находится в каталоге `/01-intro/demoapp/pkg/stringutils` 27 | -------------------------------------------------------------------------------- /00-docs/Recommendations-and-rules.MD: -------------------------------------------------------------------------------- 1 | ## Правила и рекомендации для оформления кода домашних заданий. Версия 0.3. 2 | 3 | **Формат ответа на ДЗ** 4 | Решение ДЗ должно быть в отдельном каталоге и оформлено в виде Pull Request в ветку `master`. Каждое решение должно быть в виде отдельного PR. Ответом на ДЗ является ссылка на PR. PR не должен содержать изменений, только добавления. 5 | 6 | **Структура каталогов с домашними работами** 7 | Ответы на домашние задания лучше поместить в одном каталоге с похожими именами: `homework-01`, `homework-02` и т.д. На весь проект следует создать один модуль. 8 | Пример: 9 | ``` 10 | ├───homework-01 11 | │ 12 | ├───homework-02 13 | │ │ ├───cmd 14 | │ │ │ └───app 15 | │ │ │ main.go 16 | │ │ │ 17 | │ │ └───pkg 18 | │ │ └───spider 19 | │ │ spider.go 20 | │ 21 | ├───homework-03 22 | │ 23 | │ go.mod 24 | │ go.sum 25 | │ README.md 26 | ``` 27 | 28 | **Структура каталогов Go-приложения** 29 | Проект должен иметь каталоги `pkg` и `cmd`. В каталоге `pkg` содержатся подключаемые пакеты. В каталоге `cmd` содержатся исполняемые пакеты. 30 | 31 | **Каталог пакета** 32 | Каждый пакет должен быть в отдельном каталоге. Имя пакета всегда совпадает с именем каталога. 33 | 34 | **Аварийное завершение работы программы в неисполняемом пакете** 35 | Подключаемый пакет никогда не должен аварийно завершать работу программы (`log.Fatal(), os.Exit(1)` и т.д.). Начало и завершение работы программы должно происходить в каталоге `main`. 36 | 37 | **Язык комментариев и текстовых сообщений** 38 | Для выполнения заданий можно писать комментарии и сообщения на русском или английском языках. Выбирайте тот, на котором вы можете писать без серьёзных ошибок. 39 | 40 | **Комментарии** 41 | 1. Не пишите очевидных комментариев. 42 | 2. Комментарий не должен отвечать на вопрос "что?", он должен отвечать на вопрос "зачем?". 43 | 3. Если требуется комментарий для того чтобы понять что делает ваш код, то код, вероятно, стоит переписать. 44 | 4. Смотрите примеры комментариев в стандартной библиотеке. 45 | 46 | **Передача управления программой в другой пакет навсегда** 47 | Начало и завершение программы должно происходить в каталоге `main` в функции `main()`. Недопустим вызов функции, которая намеренно завершает работу программы. 48 | 49 | **Имена функций и пакетов** 50 | https://blog.golang.org/package-names 51 | https://github.com/golang/go/wiki/CodeReviewComments#mixed-caps 52 | 53 | **Рекомендации по выбору имен** 54 | Имена пакетов должны быть короткими. Желательно из одного слова. 55 | Имена объектов внутри пакетов не должны содержать в себе имени пакета. Например, fibo.FiboNumber() - плохо, fibo.Number() - хорошо. 56 | В Go не приняты длинные имена переменных. Локальная переменная часто имеет имя из одной буквы. Чем короче имя, тем лучше. 57 | Имена функций также не должны быть очень длинными. Не принято в имени функции подробно описывать её возможности. CalculatePointToPointDistance - плохо. Distance - хорошо. 58 | 59 | **Имя пакета в тестах** 60 | В файлах тестов имя пакета должно совпадать с именем тестируемого пакета. 61 | 62 | **Фреймворки** 63 | Ни один Go-фреймворк не решает реальных проблем. Поэтому в ходе выполнения заданий не нужно использовать никакие фреймворки. 64 | 65 | **Line of sight** 66 | Удачный (нормальный) путь выполнения кода должен иметь минимальные отступы слева. То есть не нужно помещать нормальную (без ошибок) ветку кода в блоки `else` и пр. 67 | Желательно вначале обработать все ошибки, чтобы последующий код означал нормальное выполнение (happy path). 68 | 69 | **Else** 70 | Блок `else` допустим только если в блоках `if` и `else` по паре-тройке строк. Иначе используйте `if x ...` и `if !x ...`. 71 | 72 | **Чтение программы** 73 | Надо стараться организовать код так, чтобы его удобно было читать сверху вниз с минимумом возвратов. 74 | 75 | **Чрезмерное усложнение кода** 76 | Код в ответе на ДЗ должен решать поставленную задачу и не более того. 77 | Не нужно усложнять задачу, также не нужно вводить дополнительные сущности: пакеты, интерфейсы, типы данных и пр., если только это не является необходимым для решения. 78 | 79 | **Собственная рецензия** 80 | Первая рецензия на PR должна быть от автора кода. Нужно внимательно посмотреть свой код на Гитхабе и написать комментарий с рецензией. LGTM или OK вполне достаточно, но можно и подробнее. 81 | 82 | **Пустой return** 83 | Инструкция `return` всегда должна содержать имена возвращаемых переменных или их значения. Если, конечно, функция что-то возвращает. 84 | 85 | **Отсутствие проверки ошибки** 86 | Если вызываемая функция может вернуть ошибку, то эта ошибка обязательно должна быть проверена и обработана. 87 | 88 | **Отсутствия действия в случае ошибки** 89 | Реакцией на ошибку всегда должно быть действие (`return ..., continue, break`) или что-то ещё. Не достаточно просто напечатать сообщение (за редким исключением). 90 | Если ошибка не `nil`, то все остальные возвращаемые значения функции невалидны и не могут быть использованы (кроме ошибки `EOF`). 91 | 92 | **Лишние файлы в решении ДЗ** 93 | При оформлении PR удаляйте всё лишнее: файлы с примерами из лекций; скопированные файлы, не относящиеся к решению; свои заметки и пр. 94 | 95 | **.gitignore** 96 | В `.gitignore` нужно добавить `go.sum`, а также все бинарные исполняемые файлы, файлы MacOS, файлы IDE. 97 | 98 | **Конец строки** 99 | При работе с вводом из консоли не забывайте, что конец строки в Linux и MacOS - `\n`, а в Windows - `\r\n`. 100 | 101 | **Неиспользуемый код** 102 | Если вы где-то объявили интерфейс, то он должен быть реализован каким-либо типом данных. Если вы написали "заглушку" для внешней зависимости, то должен быть тест, который эту заглушку использует. -------------------------------------------------------------------------------- /01-intro/demoapp/cmd/app/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "go-core-4/01-intro/demoapp/pkg/stringutils" 7 | ) 8 | 9 | func main() { 10 | http.ListenAndServe( 11 | ":8080", 12 | http.HandlerFunc( 13 | func(w http.ResponseWriter, r *http.Request) { 14 | s := r.URL.Query()["s"] 15 | if len(s) == 0 { 16 | http.Error(w, "bad query", http.StatusBadRequest) 17 | } 18 | 19 | rev := stringutils.Rev(s[0]) 20 | 21 | w.Write([]byte(rev)) 22 | }, 23 | ), 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /01-intro/demoapp/pkg/stringutils/stringutils.go: -------------------------------------------------------------------------------- 1 | package stringutils 2 | 3 | func Rev(s string) string { 4 | runes := []rune(s) 5 | 6 | for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 { 7 | runes[i], runes[j] = runes[j], runes[i] 8 | } 9 | 10 | return string(runes) 11 | } 12 | -------------------------------------------------------------------------------- /01-intro/demoapp/pkg/stringutils/stringutils_test.go: -------------------------------------------------------------------------------- 1 | package stringutils 2 | 3 | import "testing" 4 | 5 | func TestRev(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | s string 9 | want string 10 | }{ 11 | { 12 | name: "test 1", 13 | s: "ABC", 14 | want: "CBA", 15 | }, 16 | { 17 | name: "test 1", 18 | s: "АБВ", 19 | want: "ВБА", 20 | }, 21 | } 22 | for _, tt := range tests { 23 | t.Run(tt.name, func(t *testing.T) { 24 | if got := Rev(tt.s); got != tt.want { 25 | t.Errorf("Rev() = %v, want %v", got, tt.want) 26 | } 27 | }) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /01-intro/hello/hello: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thinknetica/go_course_4/9886acf6728f566ce271056e3694247e055e1b74/01-intro/hello/hello -------------------------------------------------------------------------------- /01-intro/hello/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | const greeting = "Hello, world!" 6 | 7 | func main() { 8 | printMsg(greeting) 9 | } 10 | 11 | func printMsg(msg string) { 12 | fmt.Println(greeting) 13 | } 14 | 15 | // Output: Hello, world! 16 | -------------------------------------------------------------------------------- /02-syntax/1-basic/basic.go: -------------------------------------------------------------------------------- 1 | package basic 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | func Range() { 9 | slice := []int{10, 20, 30, 40} 10 | arr := [...]int{10, 20, 30} 11 | str := "АБВ" 12 | 13 | for i, v := range slice { 14 | fmt.Println(i, v) 15 | } 16 | fmt.Printf("\n\n") 17 | 18 | for i, v := range arr { 19 | fmt.Println(i, v) 20 | } 21 | fmt.Printf("\n\n") 22 | 23 | for i, v := range str { 24 | fmt.Println(i, v, string(v)) 25 | } 26 | fmt.Printf("\n\n") 27 | } 28 | 29 | func Vars() { 30 | var i int 31 | var j int8 = 10 32 | k := 20 33 | var l = 10 34 | println(i, j, k, l) 35 | } 36 | 37 | func Types() { 38 | type circle struct { 39 | x, y int 40 | r int 41 | } 42 | c := circle{ 43 | x: 10, 44 | y: 10, 45 | r: 20, 46 | } 47 | fmt.Printf("%+v\n", c) 48 | 49 | type myString string 50 | var s string = "ABC" 51 | var s1 myString = myString(s) 52 | fmt.Printf("Тип 1: %v\tТип2: %v\t\n", reflect.TypeOf(s), reflect.TypeOf(s1)) 53 | } 54 | 55 | func Pointers() { 56 | var s string = "ABC" 57 | var pointer *string = &s 58 | fmt.Println(*pointer, pointer) 59 | fmt.Printf("Значение: %v\tУказатель: %v\tСсылка: %v\t\n", s, pointer, &s) 60 | } 61 | 62 | func Scopes() { 63 | x := 1 64 | { 65 | x := 2 66 | fmt.Printf("x = %v\n", x) 67 | } 68 | fmt.Printf("x = %v\n", x) 69 | } 70 | -------------------------------------------------------------------------------- /02-syntax/1-basic/basic_test.go: -------------------------------------------------------------------------------- 1 | package basic 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestRange(t *testing.T) { 8 | Range() 9 | } 10 | 11 | func TestVars(t *testing.T) { 12 | Vars() 13 | } 14 | 15 | func TestTypes(t *testing.T) { 16 | Types() 17 | } 18 | 19 | func TestPointers(t *testing.T) { 20 | Pointers() 21 | } 22 | 23 | func TestScopes(t *testing.T) { 24 | Scopes() 25 | } 26 | -------------------------------------------------------------------------------- /02-syntax/2-names/pkg/names/names.go: -------------------------------------------------------------------------------- 1 | package names 2 | 3 | import zl "github.com/rs/zerolog" 4 | 5 | func index(s string, b byte) int { 6 | for i := range s { 7 | if s[i] == b { 8 | return i 9 | } 10 | } 11 | _ = zl.Level(0) 12 | 13 | return -1 14 | } 15 | -------------------------------------------------------------------------------- /02-syntax/2-names/pkg/names/names_test.go: -------------------------------------------------------------------------------- 1 | package names 2 | 3 | import "testing" 4 | 5 | const testStr = "ABCdE" 6 | 7 | func Test_index(t *testing.T) { 8 | got := index(testStr, 'd') 9 | want := 3 10 | if got != want { 11 | t.Fatalf("получили %v, ожидалось %v", got, want) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /02-syntax/3-array_slice_map/array_slice_map.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | var array = [...]int{1, 2, 3, 4, 5} // массив 7 | slice := []int{0, 1, 2, 3, 4, 5} // слайс 8 | slice = append(slice, 10) 9 | 10 | fmt.Println(slice[1:4]) 11 | 12 | _ = slice[len(slice)-1] 13 | fmt.Println("array", array, len(array), cap(array)) 14 | fmt.Println("slice", slice, len(slice), cap(slice)) 15 | 16 | slice = make([]int, 10, 20) 17 | fmt.Println("\nslice после инициализации:", slice, len(slice), cap(slice)) 18 | 19 | // ассоциативный массив 20 | var m map[int]string 21 | //m[3] = "ABC" // panic: assignment to entry in nil map 22 | //m = make(map[int]string) 23 | m = map[int]string{} 24 | m[3] = "ABC" 25 | m[6] = "DEF" 26 | for k, v := range m { 27 | fmt.Printf("ключ %d, значение %s\n", k, v) 28 | } 29 | } 30 | 31 | func sliceDimensions() { 32 | var slice []int 33 | c := cap(slice) 34 | for i := 0; i < 1_000_000; i++ { 35 | slice = append(slice, i) 36 | if cap(slice) != c { 37 | fmt.Printf("длина: %v\tемкость:%v\tкоэффициент:%v\n", len(slice), cap(slice), float64(cap(slice))/float64(c)) 38 | c = cap(slice) 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /02-syntax/3-array_slice_map/array_slice_map_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "testing" 4 | 5 | func Test_sliceDimensions(t *testing.T) { 6 | sliceDimensions() 7 | } 8 | -------------------------------------------------------------------------------- /02-syntax/4-pointers/pointers.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | var i int = 10 // переменная 7 | var p *int // указатель 8 | p = &i // взятие адреса. указателю присваивается ссылка. 9 | var p1 *interface{} 10 | _ = p1 11 | fmt.Println(i) 12 | i++ 13 | fmt.Println(i) 14 | *p++ // разыменование указателя 15 | fmt.Println(i) 16 | *(&i)++ 17 | fmt.Println(i) 18 | } 19 | 20 | // Output: 10 21 | // 11 22 | // 12 23 | // 13 24 | -------------------------------------------------------------------------------- /02-syntax/4-struct/struct.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | type point struct { 6 | lat float64 7 | lon float64 8 | } 9 | 10 | func (p point) PrintPoint() { 11 | fmt.Printf("lat:lon - %f, %f\n", p.lat, p.lon) 12 | } 13 | 14 | type object struct { 15 | name string 16 | point // встраивание, обращение по имени типа 17 | } 18 | 19 | func main() { 20 | o := object{} 21 | o.name = "Object" 22 | o.lat = 10 23 | o.lon = 20 24 | o.PrintPoint() 25 | } 26 | -------------------------------------------------------------------------------- /02-syntax/5-methods_ifaces/methods_ifaces.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | // Интерфейс. 6 | type WalkerTalker interface { 7 | Walk() // контракт интерфейса 8 | Talk() string // контракт интерфейса 9 | Eater 10 | } 11 | 12 | type MyIface interface { 13 | } 14 | 15 | type Eater interface { 16 | Eat() string 17 | } 18 | 19 | // Пользовательский тип данных. 20 | type Guy struct { 21 | Eater 22 | } 23 | 24 | // Метод (передача по значению). 25 | func (g *Guy) Walk() { 26 | fmt.Println("I walk!") 27 | } 28 | 29 | // Метод (передача по ссылке). 30 | func (g *Guy) Talk() string { 31 | return "I talk!" 32 | } 33 | 34 | func main() { 35 | // Переменная интерфейсного типа инициализирована 36 | // литералом конкретного типа. 37 | var g Guy 38 | var wt WalkerTalker = &g 39 | wt.Walk() 40 | println(wt.Talk()) 41 | println(wt.Eat()) 42 | var mi MyIface 43 | i := 10 44 | s := "str" 45 | mi = i 46 | mi = s 47 | _ = mi 48 | g.Eat() 49 | 50 | var in interface{} 51 | in = "20" 52 | in = "abc" 53 | in = []int{1, 2, 3} 54 | _ = in 55 | } 56 | 57 | type printer interface { 58 | print() 59 | } 60 | 61 | type myStr string 62 | 63 | func (s *myStr) print() { 64 | *s = "DEF" 65 | fmt.Println(*s) 66 | } 67 | -------------------------------------------------------------------------------- /02-syntax/5-methods_ifaces/methods_ifaces_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "testing" 4 | 5 | func Test_myStr_print(t *testing.T) { 6 | var s myStr = "ABC" 7 | var p printer 8 | p = &s 9 | p.print() 10 | t.Log(s) 11 | } 12 | -------------------------------------------------------------------------------- /02-syntax/6-errors/errors.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | ) 8 | 9 | // Пользовательский тип данных для ошибки. Совместим со встроенным. 10 | type customErr struct { 11 | msg string 12 | } 13 | 14 | // Выполнение контракта интерфейса. 15 | func (ce customErr) Error() string { 16 | return fmt.Sprintf("пользовательская ошибка: %s", ce.msg) 17 | } 18 | 19 | func newCustomErr(msg string) customErr { 20 | return customErr{msg: msg} 21 | } 22 | 23 | var ErrNotExists = newCustomErr("не найдено") 24 | 25 | func main() { 26 | err := errors.New("пример создания ошибки с помощью пакета errors") // с маленькой буквы! 27 | fmt.Println(err.Error()) 28 | 29 | err = fmt.Errorf("пример создания ошибки с помощью пакета fmt") 30 | fmt.Println(err) // почему без вызова метода Error()? 31 | 32 | val, err := envVar("1234") 33 | if err == ErrNotExists { 34 | fmt.Println("customErr") 35 | } 36 | if err != nil { 37 | fmt.Println(err) 38 | } 39 | fmt.Println(val) 40 | err = ErrNotExists // корректно 41 | } 42 | 43 | // envVar возвращает переменную окружения, заданную по имени. 44 | // Если переменная не найдена - возвращается ошибка. 45 | func envVar(name string) (string, error) { 46 | val := os.Getenv(name) 47 | if val == "" { 48 | return "не найдено", ErrNotExists 49 | } 50 | return val, nil 51 | } 52 | -------------------------------------------------------------------------------- /02-syntax/7-channels/channels.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func unbufChan() chan int { 8 | ch := make(chan int) 9 | go func() { 10 | defer close(ch) 11 | for i := 1; i <= 5; i++ { 12 | ch <- i 13 | } 14 | fmt.Println("Функция завершила работу!") 15 | }() 16 | return ch 17 | } 18 | 19 | func bufChan() chan int { 20 | ch := make(chan int, 10) 21 | go func() { 22 | defer close(ch) 23 | for i := 1; i <= 5; i++ { 24 | ch <- i 25 | } 26 | fmt.Println("Функция завершила работу!") 27 | }() 28 | return ch 29 | } 30 | -------------------------------------------------------------------------------- /02-syntax/7-channels/channels_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func Test_unbufChan(t *testing.T) { 9 | ch := unbufChan() 10 | for val := range ch { 11 | t.Log(val) 12 | time.Sleep(time.Second) 13 | } 14 | } 15 | 16 | func Test_bufChan(t *testing.T) { 17 | ch := bufChan() 18 | for val := range ch { 19 | t.Log(val) 20 | time.Sleep(time.Second) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /03-algorithms/1-search/search.go: -------------------------------------------------------------------------------- 1 | package bsearch 2 | 3 | import "fmt" 4 | 5 | // Simple возвращает номер элемента в массиве или -1. Используется простой поиск. 6 | func Simple(data []int, item int) int { 7 | for i := range data { 8 | if data[i] == item { 9 | return i 10 | } 11 | } 12 | return -1 13 | } 14 | 15 | // Binary возвращает номер элемента в массиве или -1. Используется бинарный поиск. 16 | func Binary(data []int, item int) int { 17 | low, high := 0, len(data)-1 18 | for low <= high { 19 | mid := (low + high) / 2 20 | if data[mid] == item { 21 | return mid 22 | } 23 | if data[mid] < item { 24 | low = mid + 1 25 | } else { 26 | high = mid - 1 27 | } 28 | } 29 | return -1 30 | } 31 | 32 | func O() { 33 | slice := []int{1, 2, 3} 34 | 35 | // O(1) 36 | fmt.Println(len(slice)) 37 | 38 | // O(N) 39 | for i := range slice { 40 | // .... 41 | 42 | // O(N^2) 43 | for j := range slice { 44 | 45 | // O(N^3) 46 | for k := range slice { 47 | _, _, _ = i, j, k 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /03-algorithms/1-search/search_test.go: -------------------------------------------------------------------------------- 1 | package bsearch 2 | 3 | import ( 4 | "math/rand" 5 | "sort" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestSearch(t *testing.T) { 11 | tests := []struct { 12 | name string 13 | data []int 14 | item int 15 | want int 16 | }{ 17 | { 18 | name: "#1", 19 | data: []int{1, 2, 4, 6, 10}, 20 | item: 6, 21 | want: 3, 22 | }, 23 | { 24 | name: "#2", 25 | data: []int{1, 2, 4, 5, 6, 10}, 26 | item: 6, 27 | want: 4, 28 | }, 29 | { 30 | name: "#3", 31 | data: []int{1, 2, 4, 5, 6, 10}, 32 | item: 16, 33 | want: -1, 34 | }, 35 | { 36 | name: "#4", 37 | data: []int{1, 2, 4, 5, 6, 10}, 38 | item: 2, 39 | want: 1, 40 | }, 41 | { 42 | name: "#5", 43 | data: []int{1, 2, 4, 5, 6, 10, 100}, 44 | item: 4, 45 | want: 2, 46 | }, 47 | } 48 | for _, tt := range tests { 49 | t.Run(tt.name, func(t *testing.T) { 50 | if got := Binary(tt.data, tt.item); got != tt.want { 51 | t.Errorf("Binary() = %v, want %v", got, tt.want) 52 | } 53 | if got := Simple(tt.data, tt.item); got != tt.want { 54 | t.Errorf("Simple() = %v, want %v", got, tt.want) 55 | } 56 | }) 57 | } 58 | } 59 | 60 | func sampleData(n int) []int { 61 | rand.Seed(time.Now().UnixNano()) 62 | var data []int 63 | for i := 0; i < n; i++ { 64 | data = append(data, rand.Intn(1000)) 65 | } 66 | 67 | sort.Slice(data, func(i, j int) bool { return data[i] < data[j] }) 68 | return data 69 | } 70 | 71 | func BenchmarkSimple(b *testing.B) { 72 | k := 1_000_000 73 | data := sampleData(k) 74 | for i := 0; i < b.N; i++ { 75 | n := rand.Intn(k) 76 | Simple(data, n) 77 | } 78 | } 79 | 80 | func BenchmarkBinary(b *testing.B) { 81 | k := 1_000_000 82 | data := sampleData(k) 83 | for i := 0; i < b.N; i++ { 84 | n := rand.Intn(k) 85 | Binary(data, n) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /03-algorithms/2-recursion/recursion.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // pow рекурсивно возводит "x" в степень "у" 8 | func pow(x, y int) int { 9 | if y == 0 { // базовый случай 10 | return 1 11 | } 12 | return x * pow(x, y-1) // рекурсивный шаг 13 | } 14 | 15 | func main() { 16 | fmt.Println("3^2:", pow(3, 2)) // 3^2: 9 17 | fmt.Println("5^3:", pow(5, 3)) // 5^3: 125 18 | } 19 | -------------------------------------------------------------------------------- /03-algorithms/3-dynamic/dynamic.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "strconv" 7 | ) 8 | 9 | // fibo рекурсивно вычисляет числа фибоначчи, используя идею динамического программирования 10 | func fibo(n int, cache map[int]int) int { 11 | // проверка наличия уже вычисленного результата в кэше 12 | if val, ok := cache[n]; ok { 13 | return val 14 | } 15 | // базовая часть рекурсии 16 | if n < 2 { 17 | return 1 18 | } 19 | // рекурсивный шаг с записью в кэш 20 | num := fibo(n-1, cache) + fibo(n-2, cache) 21 | cache[n] = num 22 | return num 23 | } 24 | 25 | func main() { 26 | cache := make(map[int]int) 27 | 28 | for { 29 | fmt.Print("Введите число: ") 30 | var in string 31 | _, err := fmt.Scanln(&in) 32 | if err != nil { 33 | log.Fatal(err) 34 | } 35 | n, err := strconv.Atoi(in) 36 | if err != nil { 37 | log.Fatal(err) 38 | } 39 | num := fibo(n, cache) 40 | fmt.Printf("Число Фибоначчи №%d = %d\n", n, num) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /03-algorithms/4-graph/graph.go: -------------------------------------------------------------------------------- 1 | package graph 2 | 3 | // Matrix - представление графа в виде матрицы смежности. 4 | // Matrix[i][j] содержит стоимость ребра между вершинами i и j. 5 | type Matrix [][]int 6 | 7 | // List - представление графа в виде списка смежности. 8 | // Лист смежности содержит для каждой вершины графа лист 9 | // смежных вершин, с указанием стоимости ребра. 10 | type List map[int][]Neighbour 11 | 12 | // Neighbour - смежная вершина и вес ребра. 13 | type Neighbour struct { 14 | Vertex int 15 | Weight int 16 | } 17 | 18 | // NewList создаёт новый список смежности. 19 | func NewList() List { 20 | var l List 21 | l = make(map[int][]Neighbour) 22 | return l 23 | } 24 | 25 | // AddNodes добавляет вершины графа. 26 | func (l List) AddNodes(nodes ...int) { 27 | for _, node := range nodes { 28 | l[node] = []Neighbour{} 29 | } 30 | } 31 | 32 | // AddEdge добавляет ребро графа. 33 | func (l List) AddEdge(node1, node2, weight int) { 34 | l[node1] = append(l[node1], Neighbour{Vertex: node2, Weight: weight}) 35 | } 36 | 37 | // Weight возвращает стоимость всех рёбер графа. 38 | func (l List) Weight() int { 39 | sum := 0 40 | for _, v := range l { 41 | for _, edge := range v { 42 | sum += edge.Weight 43 | } 44 | } 45 | return sum 46 | } 47 | 48 | // MinSpanTree - поиск минимального покрывающего (остовного) дерева 49 | // алгоритмом Прима. 50 | func (l List) MinSpanTree() List { 51 | 52 | // алгоритм: 53 | // - определяем первую вершину и добавляем в исследуемые 54 | // - цикл для исследуемых вершин: 55 | // - находим ближайшего соседа и добавляем ребро в дерево 56 | 57 | tree := NewList() 58 | current := make(map[int]bool) // вершины, исследуемые на данном шаге 59 | current[1] = true 60 | visited := make(map[int]bool) // посещённые вершины 61 | 62 | initVal := func() (v1, v2, dist int) { 63 | for k, v := range l { 64 | if k > v1 { 65 | v1, v2 = k, k 66 | } 67 | for _, item := range v { 68 | if item.Weight > dist { 69 | dist = item.Weight 70 | } 71 | } 72 | } 73 | return v1 + 1, v2 + 1, dist + 1 74 | } 75 | initV, _, _ := initVal() 76 | 77 | OUT: 78 | for { 79 | v1, v2, dist := initVal() 80 | 81 | for i := range current { 82 | v := l[i] 83 | if len(v) == 0 { 84 | continue 85 | } 86 | for _, edge := range v { 87 | if visited[edge.Vertex] { 88 | continue 89 | } 90 | 91 | if edge.Weight < dist { 92 | v1 = i 93 | v2 = edge.Vertex 94 | dist = edge.Weight 95 | } 96 | } 97 | } 98 | if v1 == initV { 99 | break OUT 100 | } 101 | visited[v1] = true 102 | visited[v2] = true 103 | neighbour := Neighbour{Vertex: v2, Weight: dist} 104 | if _, ok := tree[v1]; !ok { 105 | tree[v1] = []Neighbour{} 106 | } 107 | tree[v1] = append(tree[v1], neighbour) 108 | edges := 0 109 | for _, v := range tree { 110 | edges += len(v) 111 | } 112 | if edges+1 == len(l) { 113 | break OUT 114 | } 115 | current[v2] = true 116 | } 117 | return tree 118 | } 119 | -------------------------------------------------------------------------------- /03-algorithms/4-graph/graph_test.go: -------------------------------------------------------------------------------- 1 | package graph 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestList_MinSpanTree(t *testing.T) { 9 | l := NewList() 10 | l.AddNodes(1, 2, 3, 4, 5, 6) 11 | l.AddEdge(1, 2, 10) 12 | l.AddEdge(1, 3, 15) 13 | l.AddEdge(1, 5, 20) 14 | l.AddEdge(2, 3, 20) 15 | l.AddEdge(2, 5, 35) 16 | l.AddEdge(2, 4, 10) 17 | l.AddEdge(3, 4, 20) 18 | l.AddEdge(3, 6, 50) 19 | l.AddEdge(4, 6, 30) 20 | 21 | mst := l.MinSpanTree() 22 | fmt.Println(mst) 23 | fmt.Println(mst.Weight()) 24 | 25 | want := 85 26 | got := mst.Weight() 27 | if got != want { 28 | t.Fatalf("длина дерева %d, а ожидалось %d", got, want) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /04-datastructs/0-arrays/arrays.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/DmitriyVTitov/size" 7 | ) 8 | 9 | const N = 1_000_000_000 10 | 11 | var slice = make([]int, N) 12 | 13 | func main() { 14 | newSlice := make([]int, N+1) 15 | copy(newSlice, slice) 16 | newSlice[len(newSlice)-1] = 25 17 | 18 | s := make([]int, 1_000_000) 19 | for i := 0; i < 1_000_000; i++ { 20 | s[i] = i 21 | } 22 | _ = s 23 | 24 | newSlice[1] = 10 25 | x, y := size.Of(slice), size.Of(newSlice) 26 | fmt.Printf("Size of 'slice' is %vGB\n", x/1024/1024/1024) 27 | fmt.Printf("Size of 'newSlice' is %vGB\n", y/1024/1024/1024) 28 | fmt.Printf("Size of both slices is %vGB\n", (x+y)/1024/1024/1024) 29 | 30 | s1 := []int{1, 2, 3, 4, 5} 31 | s2 := s1[1:3] // {2, 3} 32 | fmt.Println("s2:", s2) 33 | fmt.Println("s1 before:", s1) 34 | s2[0] = 10 35 | fmt.Println("s1 after:", s1) 36 | } 37 | -------------------------------------------------------------------------------- /04-datastructs/1-list/list.go: -------------------------------------------------------------------------------- 1 | // Реализуация двусвязного списка вместе с базовыми операциями. 2 | package list 3 | 4 | import ( 5 | "fmt" 6 | ) 7 | 8 | // List - двусвязный список. 9 | type List struct { 10 | root *Elem 11 | } 12 | 13 | // Elem - элемент списка. 14 | type Elem struct { 15 | Val interface{} 16 | next, prev *Elem 17 | } 18 | 19 | // New создаёт список и возвращает указатель на него. 20 | func New() *List { 21 | var l List 22 | l.root = &Elem{} 23 | l.root.next = l.root 24 | l.root.prev = l.root 25 | return &l 26 | } 27 | 28 | // Push вставляет элемент в начало списка. 29 | func (l *List) Push(e Elem) *Elem { 30 | e.prev = l.root 31 | e.next = l.root.next 32 | l.root.next = &e 33 | if e.next != l.root { 34 | e.next.prev = &e 35 | } 36 | return &e 37 | } 38 | 39 | // String реализует интерфейс fmt.Stringer представляя список в виде строки. 40 | func (l *List) String() string { 41 | el := l.root.next 42 | var s string 43 | for el != l.root { 44 | s += fmt.Sprintf("%v ", el.Val) 45 | el = el.next 46 | } 47 | if len(s) > 0 { 48 | s = s[:len(s)-1] 49 | } 50 | return s 51 | } 52 | 53 | // Pop удаляет первый элемент списка. 54 | func (l *List) Pop() *List { 55 | return nil 56 | } 57 | 58 | // Reverse разворачивает список. 59 | func (l *List) Reverse() *List { 60 | return nil 61 | } 62 | -------------------------------------------------------------------------------- /04-datastructs/1-list/list_test.go: -------------------------------------------------------------------------------- 1 | package list 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestList_Push(t *testing.T) { 8 | l := New() 9 | l.Push(Elem{Val: 2}) 10 | l.Push(Elem{Val: 1}) 11 | 12 | got := l.String() 13 | want := "1 2" 14 | if got != want { 15 | t.Fatalf("получили %s, ожидалось %s", got, want) 16 | } 17 | } 18 | 19 | func TestList_Pop(t *testing.T) { 20 | l := New() 21 | l.Push(Elem{Val: 3}) 22 | l.Push(Elem{Val: 2}) 23 | l.Push(Elem{Val: 1}) 24 | 25 | got := l.Pop().String() 26 | want := "2 3" 27 | if got != want { 28 | t.Fatalf("получили %s, ожидалось %s", got, want) 29 | } 30 | 31 | got = l.Pop().Pop().String() 32 | want = "" 33 | if got != want { 34 | t.Fatalf("получили %s, ожидалось %s", got, want) 35 | } 36 | } 37 | 38 | func TestList_Reverse(t *testing.T) { 39 | l := New() 40 | l.Push(Elem{Val: 3}) 41 | l.Push(Elem{Val: 2}) 42 | l.Push(Elem{Val: 1}) 43 | 44 | got := l.Reverse().String() 45 | want := "3 2 1" 46 | if got != want { 47 | t.Fatalf("получили %s, ожидалось %s", got, want) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /04-datastructs/2-bst/bst.go: -------------------------------------------------------------------------------- 1 | // Пример реализации структуры данных "Двоичное дерево поиска". 2 | // Пример для простоты приведён для дерева, 3 | // содержащего в качетве значений целые числа. 4 | // Можно по аналогии со стандартной библиотекой с помощью интерфейса 5 | // обобщить на произвольные типы данных. 6 | // Для этого потребуется контракт на функцию сравнения элементов. 7 | package main 8 | 9 | import "fmt" 10 | 11 | // Двоичное дерево поиска 12 | type Tree struct { 13 | root *Element 14 | } 15 | 16 | // Лист дерева. 17 | type Element struct { 18 | left, right *Element 19 | Value int 20 | } 21 | 22 | // Insert вставляет элемент в дерево. 23 | func (t *Tree) Insert(x int) { 24 | e := &Element{Value: x} 25 | if t.root == nil { 26 | t.root = e 27 | return 28 | } 29 | insert(t.root, e) 30 | } 31 | 32 | // inset рекурсивно вставляет элемент в нужный уровень дерева. 33 | func insert(node, new *Element) { 34 | if new.Value < node.Value { 35 | if node.left == nil { 36 | node.left = new 37 | return 38 | } 39 | insert(node.left, new) 40 | } 41 | if new.Value >= node.Value { 42 | if node.right == nil { 43 | node.right = new 44 | return 45 | } 46 | insert(node.right, new) 47 | } 48 | } 49 | 50 | // Search ищет значение в дереве. 51 | func (t *Tree) Search(x int) bool { 52 | return search(t.root, x) 53 | } 54 | 55 | func search(el *Element, x int) bool { 56 | if el == nil { 57 | return false 58 | } 59 | if el.Value == x { 60 | return true 61 | } 62 | if el.Value < x { 63 | return search(el.right, x) 64 | } 65 | return search(el.left, x) 66 | } 67 | 68 | // String реализует интерфейс Stringer для дерева. 69 | func (t Tree) String() string { 70 | return prettyPrint(t.root, 0) 71 | } 72 | 73 | // prettyPrint печатает дерево в виде дерева :) 74 | func prettyPrint(e *Element, spaces int) (res string) { 75 | if e == nil { 76 | return res 77 | } 78 | spaces++ 79 | res += prettyPrint(e.right, spaces) 80 | for i := 0; i < spaces; i++ { 81 | res += "\t" 82 | } 83 | res += fmt.Sprintf("%d\n", e.Value) 84 | res += prettyPrint(e.left, spaces) 85 | return res 86 | } 87 | 88 | func initTree() *Tree { 89 | var t Tree 90 | t.Insert(10) 91 | t.Insert(15) 92 | t.Insert(15) 93 | t.Insert(15) 94 | t.Insert(15) 95 | t.Insert(25) 96 | t.Insert(30) 97 | t.Insert(35) 98 | t.Insert(5) 99 | t.Insert(20) 100 | t.Insert(1) 101 | t.Insert(2) 102 | t.Insert(6) 103 | return &t 104 | } 105 | 106 | func main() { 107 | t := initTree() 108 | fmt.Println(t) 109 | } 110 | 111 | // Output: 112 | // 35 113 | // 30 114 | // 25 115 | // 20 116 | // 15 117 | // 10 118 | // 6 119 | // 5 120 | // 2 121 | // 1 122 | -------------------------------------------------------------------------------- /05-io/1-net_io/net_io.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net" 6 | "strings" 7 | "time" 8 | ) 9 | 10 | // Сетевой адрес. 11 | // 12 | // Служба будет слушать запросы на всех IP-адресах 13 | // компьютера на порту 12345. 14 | // Нпример, 127.0.0.1:12345 15 | const addr = "0.0.0.0:12345" 16 | 17 | // Протокол сетевой службы. 18 | const proto = "tcp4" 19 | 20 | func main() { 21 | // Запуск сетевой службы про протоколу TCP 22 | // на порту 12345. 23 | listener, err := net.Listen(proto, addr) 24 | if err != nil { 25 | log.Fatal(err) 26 | } 27 | 28 | // Подключения обрабатываются в бесконечном цикле. 29 | // Иначе после обслуживания первого подключения сервер 30 | // завершит работу. 31 | for { 32 | // Принимаем подключение. 33 | conn, err := listener.Accept() 34 | if err != nil { 35 | log.Fatal(err) 36 | } 37 | // Вызов обработчика подключения. 38 | handleConn(conn) 39 | } 40 | } 41 | 42 | // Обработчик. Вызывается для каждого соединения. 43 | func handleConn(conn net.Conn) { 44 | 45 | // Чтение сообщения от клиента. 46 | var req []byte 47 | var buf = make([]byte, 2) 48 | for { 49 | _, err := conn.Read(buf) 50 | if err != nil { 51 | log.Println(err) 52 | return 53 | } 54 | end := false 55 | for _, b := range buf { 56 | if b == '\n' { 57 | end = true 58 | break 59 | } 60 | req = append(req, b) 61 | } 62 | if end { 63 | break 64 | } 65 | } 66 | 67 | // Удаление символов конца строки. 68 | msg := strings.TrimSuffix(string(req), "\n") 69 | msg = strings.TrimSuffix(msg, "\r") 70 | 71 | // Если получили "time" - пишем время в соединение. 72 | if msg == "time" { 73 | n, err := conn.Write([]byte(time.Now().String() + "\n")) 74 | if err != nil { 75 | log.Printf("ошибка: %v", err) 76 | return 77 | } 78 | log.Printf("клиенту отправлено %d байт", n) 79 | } 80 | 81 | // Закрытие соединения. 82 | conn.Close() 83 | } 84 | -------------------------------------------------------------------------------- /05-io/1-net_io/net_io_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "log" 6 | "net" 7 | "sync" 8 | "testing" 9 | ) 10 | 11 | func Test_handleConn(t *testing.T) { 12 | // Имитация полнодуплексного 13 | // сетевого соединения. 14 | srv, cl := net.Pipe() 15 | 16 | // Мы должны дождаться 17 | // завершения всех потоков. 18 | var wg sync.WaitGroup 19 | wg.Add(1) 20 | 21 | // Обработчик подключения запускается 22 | // в отдельном потоке. 23 | go func() { 24 | // Обработчику передается один из 25 | // концов виртуальной трубы. 26 | handleConn(srv) 27 | srv.Close() 28 | wg.Done() 29 | }() 30 | 31 | // В основном потоке теста клиент отправляет 32 | // сообщения в трубу. 33 | // Эти сообщения сервер прочитает в соседнем потоке. 34 | _, err := cl.Write([]byte("time\n")) 35 | if err != nil { 36 | log.Fatal(err) 37 | } 38 | 39 | // Чтенеие ответа от сервера. 40 | reader := bufio.NewReader(cl) 41 | b, err := reader.ReadBytes('\n') 42 | if err != nil { 43 | log.Fatal(err) 44 | } 45 | 46 | // Проверка ответа. 47 | if len(b) < 10 { 48 | t.Error("время не получено") 49 | } 50 | wg.Wait() 51 | cl.Close() 52 | t.Log(string(b)) 53 | } 54 | -------------------------------------------------------------------------------- /05-io/2-writer-example/strings.txt: -------------------------------------------------------------------------------- 1 | ГЛАВРЫБА -------------------------------------------------------------------------------- /05-io/2-writer-example/writer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io" 5 | "log" 6 | "os" 7 | ) 8 | 9 | // sendReversedString разворачивает строку и отправляет её получателю. 10 | func sendReversedString(s string, w io.Writer) error { 11 | runes := []rune(s) 12 | for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 { 13 | runes[i], runes[j] = runes[j], runes[i] 14 | } 15 | 16 | _, err := w.Write([]byte(string(runes))) 17 | return err 18 | } 19 | 20 | func main() { 21 | f, err := os.Create("./strings.txt") 22 | if err != nil { 23 | log.Fatal(err) 24 | } 25 | defer f.Close() 26 | 27 | // Вывод на примере файла. 28 | err = sendReversedString("АБЫРВАЛГ", f) 29 | if err != nil { 30 | log.Fatal(err) 31 | } 32 | 33 | // Вывод на примере консоли. 34 | err = sendReversedString("АБЫРВАЛГ", os.Stdout) 35 | if err != nil { 36 | log.Fatal(err) 37 | } 38 | } 39 | 40 | func get(r io.Reader) ([]byte, error) { 41 | return io.ReadAll(r) 42 | /*/ 43 | var b []byte 44 | var buf = make([]byte, 10) 45 | for { 46 | _, err := r.Read(buf) 47 | if err == io.EOF { 48 | break 49 | } 50 | if err != nil { 51 | log.Println(err) 52 | return nil, err 53 | } 54 | b = append(b, buf...) 55 | } 56 | // Здесь какая-то логика. 57 | return b, nil*/ 58 | } 59 | -------------------------------------------------------------------------------- /05-io/3-bufio_example/bufio.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "io" 6 | "log" 7 | "os" 8 | ) 9 | 10 | func main() { 11 | f, err := os.Open("./bufio.go") 12 | if err != nil { 13 | log.Fatal(err) 14 | } 15 | 16 | b, err := get(f) 17 | if err != nil { 18 | log.Fatal(err) 19 | } 20 | 21 | f.Close() 22 | 23 | f, err = os.Create("./bufio_copy.go") 24 | if err != nil { 25 | log.Fatal(err) 26 | } 27 | 28 | err = store(f, b) 29 | if err != nil { 30 | log.Fatal(err) 31 | } 32 | 33 | f.Close() 34 | } 35 | 36 | func store(w io.Writer, b []byte) error { 37 | // Здесь должна быть некотороая логика. 38 | // После обработки данных мы их записываем туда, 39 | // куда хочет вызывающий код. 40 | _, err := w.Write(b) 41 | return err 42 | } 43 | 44 | func get(r io.Reader) ([]byte, error) { 45 | // буфер уже является частью сканера 46 | scanner := bufio.NewScanner(r) 47 | 48 | var b []byte 49 | for scanner.Scan() { 50 | b = append(b, []byte(scanner.Text()+"\n")...) 51 | } 52 | if err := scanner.Err(); err != nil { 53 | return nil, err 54 | } 55 | 56 | // Здесь какая-то логика. 57 | return b, nil 58 | } 59 | -------------------------------------------------------------------------------- /05-io/4-files/files.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | ) 8 | 9 | func main() { 10 | f, err := os.Create("./file.txt") 11 | if err != nil { 12 | log.Println(err) 13 | return 14 | } 15 | defer f.Close() 16 | 17 | err = os.WriteFile(f.Name(), []byte("Текст"), 0666) 18 | if err != nil { 19 | log.Println(err) 20 | return 21 | } 22 | 23 | data, err := os.ReadFile(f.Name()) 24 | if err != nil { 25 | log.Println(err) 26 | return 27 | } 28 | fmt.Printf("Данные файла:\n%s\n", data) 29 | 30 | // вариант чтения с помощью io.Reader 31 | var file *os.File 32 | file, err = os.Open(f.Name()) 33 | if err != nil { 34 | log.Println(err) 35 | return 36 | } 37 | defer file.Close() 38 | 39 | // что здесь не правильно? 40 | buf := make([]byte, 6) 41 | n, err := file.Read(buf) 42 | if err != nil { 43 | log.Println(err) 44 | return 45 | } 46 | buf = buf[:n] 47 | fmt.Printf("Данные файла:\n%s\n", buf) 48 | } 49 | -------------------------------------------------------------------------------- /05-io/5-input/input.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "os" 7 | "strings" 8 | ) 9 | 10 | func main() { 11 | reader := bufio.NewReader(os.Stdin) // буфер для os.Stdin 12 | for { 13 | fmt.Print("NewReader-> ") 14 | text, _ := reader.ReadString('\n') // чтение строки (до символа перевода) 15 | text = strings.TrimSuffix(text, "\n") // удаление перевода строки 16 | text = strings.TrimSuffix(text, "\r") // удаление перевода строки для Windows 17 | if text == "exit" { 18 | break 19 | } 20 | fmt.Println("echo:", text) 21 | } 22 | 23 | scanner := bufio.NewScanner(os.Stdin) 24 | fmt.Print("NewScanner-> ") 25 | for scanner.Scan() { 26 | if scanner.Text() == "exit" { 27 | break 28 | } 29 | fmt.Println(scanner.Text()) 30 | fmt.Print("NewScanner-> ") 31 | } 32 | 33 | for { 34 | fmt.Print("fmt.Scanln-> ") 35 | var s string 36 | fmt.Scanln(&s) 37 | if s == "exit" { 38 | break 39 | } 40 | fmt.Println(s) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /05-io/6-flags/flags.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | ) 7 | 8 | func main() { 9 | s := flag.String("filename", "file.txt", "имя файла") 10 | 11 | var n int 12 | flag.IntVar(&n, "n", 10, "количество") 13 | flag.IntVar(&n, "number", 10, "количество") 14 | 15 | flag.Parse() 16 | 17 | flag.PrintDefaults() 18 | 19 | fmt.Println(*s, n) 20 | } 21 | -------------------------------------------------------------------------------- /05-io/7-json_serialize/json.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | ) 7 | 8 | type Point struct { 9 | Lon float64 `json:"1"` 10 | Lat float64 `json:"2"` 11 | } 12 | 13 | func main() { 14 | var points []Point 15 | for i := 0; i < 100; i++ { 16 | points = append(points, Point{Lon: float64(i), Lat: float64(i)}) 17 | } 18 | 19 | b, _ := json.MarshalIndent(points, "", " ") 20 | fmt.Println(string(b)) 21 | } 22 | -------------------------------------------------------------------------------- /06-oop/1-methods/methods.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // Course - курс по Go. 8 | type Course struct { 9 | author string 10 | } 11 | 12 | // SetAuthor устанавливает имя автора. 13 | func (c *Course) SetAuthor(name string) { 14 | c.author = name 15 | } 16 | 17 | // Author возвращает имя атора курса. 18 | func (c Course) Author() string { 19 | return c.author 20 | } 21 | 22 | func main() { 23 | c := new(Course) // new создаёт переменную и возвращает указатель на неё 24 | c.SetAuthor("1") 25 | fmt.Println(c.Author()) 26 | 27 | var course Course 28 | course.SetAuthor("2") 29 | fmt.Println(course.Author()) // Что будет выведено здесь? 30 | 31 | // The rule about pointers vs. values for receivers is that value methods 32 | // can be invoked on pointers and values, 33 | // but pointer methods can only be invoked on pointers. 34 | } 35 | 36 | // *** Вопрос: что будет выведено, если получатели методов сделать значениями? 37 | -------------------------------------------------------------------------------- /06-oop/2-dip/ifaces.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "log" 6 | ) 7 | 8 | // Logger - журнал. 9 | type Logger interface { 10 | Log(string) error 11 | } 12 | 13 | // DBLogger - журнал в БД. 14 | type DBLogger struct { 15 | connString string // строка для подключения к БД 16 | } 17 | 18 | // Log записывает сообщение в журнал в БД. 19 | func (dbl *DBLogger) Log(msg string) error { 20 | if dbl.connString == "" { 21 | return errors.New("БД недоступна") 22 | } 23 | // выполняем запись сообщения в БД 24 | return nil 25 | } 26 | 27 | type CustomLogger struct { 28 | Logger 29 | } 30 | 31 | // MemLogger - заглушка журнала в памяти для тестов. 32 | type MemLogger struct{} 33 | 34 | // Log ничего не делает. Обратите внимание на отсутствие имён у получателя и аргумента. 35 | func (*MemLogger) Log(string) error { 36 | return nil 37 | } 38 | 39 | func main() { 40 | l := new(CustomLogger) 41 | err := logMsg(l, "сообщение") 42 | if err != nil { 43 | log.Println(err) 44 | return 45 | } 46 | } 47 | 48 | type Server struct { 49 | log Logger 50 | } 51 | 52 | func New(log Logger) *Server { 53 | s := &Server{log: log} 54 | return s 55 | } 56 | 57 | // logMsg записывает сообщение в журнал 58 | func logMsg(l Logger, msg string) error { 59 | // CODE 60 | err := l.Log(msg) 61 | return err 62 | } 63 | -------------------------------------------------------------------------------- /06-oop/2-dip/ifaces_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func Test_logMsg(t *testing.T) { 8 | s := New(&MemLogger{}) 9 | _ = s 10 | 11 | l := new(CustomLogger) 12 | err := logMsg(l, "msg") 13 | if err != nil { 14 | t.Fatal(err) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /06-oop/3-embedding/embedding.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type computer struct { 8 | model string 9 | processor 10 | } 11 | 12 | type processor struct { 13 | model string 14 | cores int 15 | } 16 | 17 | func (c *computer) cpuinfo() string { 18 | return "computer cpuinfo()" 19 | } 20 | 21 | func (p *processor) cpuinfo() string { 22 | return fmt.Sprintf("%s, %d ядер", p.model, p.cores) 23 | } 24 | 25 | func (c *computer) String() string { 26 | return fmt.Sprintf("Компьютер \"%s\".\nПроцессор \"%s\".\n", c.model, c.cpuinfo()) 27 | } 28 | 29 | func main() { 30 | comp := &computer{ 31 | model: "Компьютер игровой", 32 | processor: processor{ 33 | model: "Байкал", 34 | cores: 8, 35 | }, 36 | } 37 | 38 | fmt.Println(comp.cpuinfo()) 39 | } 40 | -------------------------------------------------------------------------------- /06-oop/4-constructor/constructor.go: -------------------------------------------------------------------------------- 1 | package guitar 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // Guitar - гитара. 8 | type Guitar struct { 9 | manufacturer string 10 | model string 11 | 12 | Tabs map[string]string 13 | } 14 | 15 | // New - конструктор. 16 | func New(manufacturer, model string) *Guitar { 17 | g := Guitar{ 18 | manufacturer: manufacturer, // имена совпадают 19 | model: model, // но это не проблема 20 | } 21 | g.Tabs = make(map[string]string) 22 | 23 | return &g // явно указываем, что возвращается ссылка 24 | } 25 | 26 | // Info возвращает сведения о гитаре. 27 | func (g *Guitar) Info() string { 28 | return fmt.Sprintf("Гитара %s %s", g.manufacturer, g.model) 29 | } 30 | -------------------------------------------------------------------------------- /06-oop/4-constructor/constructor_test.go: -------------------------------------------------------------------------------- 1 | package guitar 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestNew(t *testing.T) { 8 | g := New("Fender", "Stratocaster") 9 | got := g.Info() 10 | want := "Гитара Fender Stratocaster" 11 | if got != want { 12 | t.Errorf("получили %s, ожидалось %s", got, want) 13 | } 14 | } 15 | 16 | func TestGuitar(t *testing.T) { 17 | var g Guitar 18 | g.Tabs = make(map[string]string) 19 | g.Tabs["Metallica"] = "One" 20 | } 21 | -------------------------------------------------------------------------------- /06-oop/5-hw/hw.go: -------------------------------------------------------------------------------- 1 | package hw 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | ) 7 | 8 | // По условиям задачи, координаты не могут быть меньше 0. 9 | 10 | type Geom struct { 11 | X1, Y1, X2, Y2 float64 12 | } 13 | 14 | func (geom Geom) CalculateDistance() (distance float64) { 15 | 16 | if geom.X1 < 0 || geom.X2 < 0 || geom.Y1 < 0 || geom.Y2 < 0 { 17 | fmt.Println("Координаты не могут быть меньше нуля") 18 | return -1 19 | } else { 20 | distance = math.Sqrt(math.Pow(geom.X2-geom.X1, 2) + math.Pow(geom.Y2-geom.Y1, 2)) 21 | } 22 | 23 | // возврат расстояния между точками 24 | return distance 25 | } 26 | -------------------------------------------------------------------------------- /06-oop/5-hw/hw_test.go: -------------------------------------------------------------------------------- 1 | package hw 2 | 3 | import "testing" 4 | 5 | func TestGeom_CalculateDistance(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | geom Geom 9 | wantDistance float64 10 | }{ 11 | { 12 | name: "#1", 13 | geom: Geom{X1: 1, Y1: 1, X2: 4, Y2: 5}, 14 | wantDistance: 5, 15 | }, 16 | } 17 | for _, tt := range tests { 18 | t.Run(tt.name, func(t *testing.T) { 19 | if gotDistance := tt.geom.CalculateDistance(); gotDistance != tt.wantDistance { 20 | t.Errorf("Geom.CalculateDistance() = %v, want %v", gotDistance, tt.wantDistance) 21 | } 22 | }) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /07-testing/1-simple/simple.go: -------------------------------------------------------------------------------- 1 | package simple 2 | 3 | func sum(a, b int) int { 4 | return a + b 5 | } 6 | -------------------------------------------------------------------------------- /07-testing/1-simple/simple_test.go: -------------------------------------------------------------------------------- 1 | package simple 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | // Тест всегда имеет определённую сигнатуру: TestFuncName(t *testing.T) { ... } 8 | func Test_sum(t *testing.T) { 9 | a, b := 3, 4 // тестовый пример 10 | want := 7 // заранее вычисленный результат 11 | 12 | got := sum(a, b) // вызов тестируемого кода 13 | 14 | if got != want { // сравнение результата с правильным значением 15 | t.Fatalf("получили %d, ожидалось %d", got, want) 16 | } 17 | 18 | t.Log("OK") 19 | } 20 | 21 | // === RUN Test_sum 22 | // --- PASS: Test_sum (0.00s) 23 | // PASS 24 | // ok go-core/8-testing/simple 0.138s 25 | -------------------------------------------------------------------------------- /07-testing/2-testmain/testmain.go: -------------------------------------------------------------------------------- 1 | package testmain 2 | 3 | import "github.com/jackc/pgx/v4" 4 | 5 | type Service struct { 6 | db *pgx.Conn 7 | } 8 | 9 | type Product struct { 10 | Name string 11 | Price int 12 | } 13 | 14 | func (s *Service) Products() []Product { 15 | data := []Product{ 16 | { 17 | Name: "Компьютер", 18 | Price: 20_000, 19 | }, 20 | } 21 | return data 22 | } 23 | -------------------------------------------------------------------------------- /07-testing/2-testmain/testmain_test.go: -------------------------------------------------------------------------------- 1 | package testmain 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "os" 7 | "reflect" 8 | "testing" 9 | 10 | "github.com/jackc/pgx/v4" 11 | ) 12 | 13 | var testService *Service 14 | 15 | func TestMain(m *testing.M) { 16 | conn, err := pgx.Connect(context.Background(), "postgres://user:pwd@server/database") 17 | if err != nil { 18 | log.Println(err) 19 | os.Exit(1) 20 | } 21 | 22 | testService = &Service{ 23 | db: conn, 24 | } 25 | 26 | m.Run() 27 | // Здесь может идти освобождение ресурсов. 28 | } 29 | 30 | func TestService_Products(t *testing.T) { 31 | got := testService.Products() 32 | want := []Product{ 33 | { 34 | Name: "Компьютер", 35 | Price: 20_000, 36 | }, 37 | } 38 | if !reflect.DeepEqual(got, want) { 39 | t.Errorf("Service.Products() = %v, want %v", got, want) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /07-testing/3-table/table.go: -------------------------------------------------------------------------------- 1 | package table 2 | 3 | // reverse разворачивает строку. 4 | func reverse(s string) string { 5 | runes := []rune(s) 6 | 7 | for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 { 8 | runes[i], runes[j] = runes[j], runes[i] 9 | } 10 | 11 | return string(runes) 12 | } 13 | -------------------------------------------------------------------------------- /07-testing/3-table/table_test.go: -------------------------------------------------------------------------------- 1 | package table 2 | 3 | import "testing" 4 | 5 | func Test_reverse(t *testing.T) { 6 | type args struct { 7 | s string 8 | } 9 | tests := []struct { 10 | name string 11 | args args 12 | want string 13 | }{ 14 | { 15 | name: "Тест №1", 16 | args: args{ 17 | s: "string", 18 | }, 19 | want: "gnirts", 20 | }, 21 | { 22 | name: "Тест №2", 23 | args: args{ 24 | s: "ABCdefg", 25 | }, 26 | want: "gfedCBA", 27 | }, 28 | { 29 | name: "Тест №3", 30 | args: args{ 31 | s: "", 32 | }, 33 | want: "", 34 | }, 35 | { 36 | name: "Тест №4", 37 | args: args{ 38 | s: "абв", 39 | }, 40 | want: "вба", 41 | }, 42 | { 43 | name: "Тест №4", 44 | args: args{ 45 | s: "абвг", 46 | }, 47 | want: "гвба", 48 | }, 49 | } 50 | for _, tt := range tests { 51 | t.Run(tt.name, func(t *testing.T) { 52 | if got := reverse(tt.args.s); got != tt.want { 53 | t.Errorf("reverse() = %v, want %v", got, tt.want) 54 | } 55 | }) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /07-testing/4-refactoring/refactoring.go: -------------------------------------------------------------------------------- 1 | package refactoring 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "net/http" 7 | ) 8 | 9 | // Rates - курсы валют. 10 | type Rates struct { 11 | Valute struct { 12 | USD struct { 13 | ID string `json:"ID"` 14 | NumCode string `json:"NumCode"` 15 | CharCode string `json:"CharCode"` 16 | Nominal int `json:"Nominal"` 17 | Name string `json:"Name"` 18 | Value float64 `json:"Value"` 19 | Previous float64 `json:"Previous"` 20 | } `json:"USD"` 21 | } 22 | } 23 | 24 | func rublesToUSD(rubles float64) (float64, error) { 25 | ratio, err := data() 26 | if err != nil { 27 | return 0, err 28 | } 29 | 30 | return calc(rubles, ratio), nil 31 | } 32 | 33 | func data() (float64, error) { 34 | var data Rates 35 | const url = "https://www.cbr-xml-daily.ru/daily_json.js" 36 | resp, err := http.Get(url) 37 | if err != nil { 38 | return 0, err 39 | } 40 | 41 | err = json.NewDecoder(resp.Body).Decode(&data) 42 | if err != nil { 43 | return 0, err 44 | } 45 | 46 | if data.Valute.USD.Value == 0 { 47 | return 0, errors.New("деление на 0") 48 | } 49 | 50 | return data.Valute.USD.Value, nil 51 | } 52 | 53 | func calc(rubles float64, ratio float64) float64 { 54 | return rubles / ratio 55 | } 56 | -------------------------------------------------------------------------------- /07-testing/4-refactoring/refactoring_test.go: -------------------------------------------------------------------------------- 1 | package refactoring 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func Test_rublesToUSD(t *testing.T) { 8 | const rubles = 1000 9 | res, err := rublesToUSD(rubles) 10 | if err != nil { 11 | t.Fatal(err) 12 | } 13 | t.Logf("%v рублей - это %.2f долларов\n", rubles, res) 14 | } 15 | 16 | func Test_calc(t *testing.T) { 17 | want := 10 18 | got := calc(1000, 100) 19 | if got != float64(want) { 20 | t.Fatalf("got %v, want %v", got, want) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /07-testing/5-db/db.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | type Product struct { 4 | Name string 5 | Price int 6 | } 7 | 8 | func Products() ([]Product, error) { 9 | data := []Product{ 10 | { 11 | Name: "Пепелац", 12 | Price: 5, // в Кэцэ 13 | }, 14 | } 15 | return data, nil 16 | } 17 | 18 | func NewProduct(Product) error { 19 | return nil 20 | } 21 | 22 | func UpdateProduct(Product) error { 23 | return nil 24 | } 25 | 26 | func DeleteProduct(Product) error { 27 | return nil 28 | } 29 | -------------------------------------------------------------------------------- /07-testing/5-db/db_test.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestProductCRUD(t *testing.T) { 8 | if !testing.Short() { 9 | t.Skip() 10 | } 11 | 12 | var item = Product{ 13 | Name: "Пепелац", 14 | Price: 5, // в Кэцэ 15 | } 16 | 17 | err := NewProduct(item) 18 | if err != nil { 19 | t.Error(err) 20 | } 21 | 22 | data, err := Products() 23 | if err != nil { 24 | t.Error(err) 25 | } 26 | found := false 27 | for _, p := range data { 28 | if p.Name == item.Name { 29 | found = true 30 | } 31 | } 32 | if !found { 33 | t.Errorf("не найден продукт: %v", item) 34 | } 35 | 36 | err = UpdateProduct(item) 37 | if err != nil { 38 | t.Error(err) 39 | } 40 | data, err = Products() 41 | if err != nil { 42 | t.Error(err) 43 | } 44 | found = false 45 | for _, p := range data { 46 | if p.Name == item.Name { 47 | found = true 48 | } 49 | } 50 | if !found { 51 | t.Errorf("не найден продукт: %v", item) 52 | } 53 | 54 | err = DeleteProduct(item) 55 | if err != nil { 56 | t.Error(err) 57 | } 58 | data, err = Products() 59 | if err != nil { 60 | t.Error(err) 61 | } 62 | found = false 63 | for _, p := range data { 64 | if p.Name == item.Name { 65 | found = true 66 | } 67 | } 68 | if !found { 69 | t.Errorf("не удалён продукт: %v", item) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /07-testing/6-handler/handler.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | ) 7 | 8 | // API - API. 9 | type API struct { 10 | dbConn string 11 | } 12 | 13 | type product struct { 14 | Name string 15 | Price int 16 | } 17 | 18 | func (api API) ProductsHandler(w http.ResponseWriter, r *http.Request) { 19 | data := []product{ 20 | { 21 | Name: "Книга", 22 | Price: 1000, 23 | }, 24 | } 25 | 26 | err := json.NewEncoder(w).Encode(data) 27 | if err != nil { 28 | http.Error(w, err.Error(), http.StatusInternalServerError) 29 | return 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /07-testing/6-handler/handler_test.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "net/http" 7 | "net/http/httptest" 8 | "testing" 9 | ) 10 | 11 | var api API 12 | 13 | func TestAPI_ProductsHandler(t *testing.T) { 14 | // Создание объекта запроса. 15 | req := httptest.NewRequest(http.MethodGet, "/api/HANDLER", nil) 16 | 17 | // Создание объекта для записи ответа. 18 | rr := httptest.NewRecorder() 19 | 20 | // Вызов тестируемого обработчика. 21 | api.ProductsHandler(rr, req) 22 | 23 | // Проверка ответа. 24 | if !(rr.Code == http.StatusOK) { 25 | t.Errorf("код неверен: получили %d, а хотели %d", rr.Code, http.StatusOK) 26 | } 27 | resp := rr.Result() 28 | body, _ := ioutil.ReadAll(resp.Body) 29 | var data []product 30 | json.Unmarshal(body, &data) 31 | t.Logf("Ответ сервера:\n%v\n", data) 32 | } 33 | -------------------------------------------------------------------------------- /07-testing/7-tdd/tdd.go: -------------------------------------------------------------------------------- 1 | package tdd 2 | 3 | func Fact(n int) int { 4 | if n < 1 { 5 | return 1 6 | } 7 | 8 | return n * Fact(n-1) 9 | } 10 | -------------------------------------------------------------------------------- /07-testing/7-tdd/tdd_test.go: -------------------------------------------------------------------------------- 1 | package tdd 2 | 3 | import "testing" 4 | 5 | func TestFact(t *testing.T) { 6 | type args struct { 7 | n int 8 | } 9 | tests := []struct { 10 | name string 11 | args args 12 | want int 13 | }{ 14 | { 15 | name: "Test #1", 16 | args: args{n: 1}, 17 | want: 1, 18 | }, 19 | { 20 | name: "Test #2", 21 | args: args{n: 3}, 22 | want: 6, 23 | }, 24 | { 25 | name: "Test #2", 26 | args: args{n: 0}, 27 | want: 1, 28 | }, 29 | } 30 | for _, tt := range tests { 31 | t.Run(tt.name, func(t *testing.T) { 32 | if got := Fact(tt.args.n); got != tt.want { 33 | t.Errorf("Fact() = %v, want %v", got, tt.want) 34 | } 35 | }) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /07-testing/8-benchmarks/search.go: -------------------------------------------------------------------------------- 1 | package bsearch 2 | 3 | // Binary возвращает номер элемента в массиве или -1. Используется бинарный поиск. 4 | func Binary(data []int, item int) int { 5 | low, high := 0, len(data)-1 6 | for low <= high { 7 | mid := (low + high) / 2 8 | if data[mid] == item { 9 | return mid 10 | } 11 | if data[mid] < item { 12 | low = mid + 1 13 | } else { 14 | high = mid - 1 15 | } 16 | } 17 | return -1 18 | } 19 | 20 | // Simple возвращает номер элемента в массиве или -1. Используется простой поиск. 21 | func Simple(data []int, item int) int { 22 | for i := range data { 23 | if data[i] == item { 24 | return i 25 | } 26 | } 27 | return -1 28 | } 29 | -------------------------------------------------------------------------------- /07-testing/8-benchmarks/search_test.go: -------------------------------------------------------------------------------- 1 | package bsearch 2 | 3 | import ( 4 | "math/rand" 5 | "sort" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestSearches(t *testing.T) { 11 | tests := []struct { 12 | name string 13 | data []int 14 | item int 15 | want int 16 | }{ 17 | { 18 | name: "#1", 19 | data: []int{1, 2, 4, 6, 10}, 20 | item: 6, 21 | want: 3, 22 | }, 23 | { 24 | name: "#2", 25 | data: []int{1, 2, 4, 5, 6, 10}, 26 | item: 6, 27 | want: 4, 28 | }, 29 | { 30 | name: "#3", 31 | data: []int{1, 2, 4, 5, 6, 10}, 32 | item: 16, 33 | want: -1, 34 | }, 35 | { 36 | name: "#4", 37 | data: []int{1, 2, 4, 5, 6, 10}, 38 | item: 2, 39 | want: 1, 40 | }, 41 | { 42 | name: "#5", 43 | data: []int{1, 2, 4, 5, 6, 10, 100}, 44 | item: 4, 45 | want: 2, 46 | }, 47 | } 48 | for _, tt := range tests { 49 | t.Run(tt.name, func(t *testing.T) { 50 | if got := Binary(tt.data, tt.item); got != tt.want { 51 | t.Errorf("Binary() = %v, want %v", got, tt.want) 52 | } 53 | if got := Simple(tt.data, tt.item); got != tt.want { 54 | t.Errorf("Simple() = %v, want %v", got, tt.want) 55 | } 56 | }) 57 | } 58 | } 59 | 60 | func sampleData() []int { 61 | rand.Seed(time.Now().UnixNano()) 62 | var data []int 63 | for i := 0; i < 1_000_000; i++ { 64 | data = append(data, rand.Intn(1000)) 65 | } 66 | 67 | sort.Slice(data, func(i, j int) bool { return data[i] < data[j] }) 68 | return data 69 | } 70 | 71 | func BenchmarkBinary(b *testing.B) { 72 | data := sampleData() 73 | b.ResetTimer() 74 | 75 | for i := 0; i < b.N; i++ { 76 | n := rand.Intn(1000) 77 | res := Binary(data, n) 78 | _ = res 79 | } 80 | } 81 | 82 | func BenchmarkSimple(b *testing.B) { 83 | data := sampleData() 84 | b.ResetTimer() 85 | 86 | for i := 0; i < b.N; i++ { 87 | n := rand.Intn(1000) 88 | res := Simple(data, n) 89 | _ = res 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /07-testing/flags/flag_test.go: -------------------------------------------------------------------------------- 1 | package flags 2 | 3 | import ( 4 | "flag" 5 | "testing" 6 | ) 7 | 8 | var f = flag.Bool("int", false, "") 9 | 10 | func TestFunc(t *testing.T) { 11 | flag.Parse() 12 | if *f { 13 | t.Log("FLAG") 14 | } 15 | if !*f { 16 | t.Log("NO FLAG") 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /08-prof_debug/1-bench_profile/search.go: -------------------------------------------------------------------------------- 1 | package bsearch 2 | 3 | // Binary возвращает номер элемента в массиве или -1. Используется бинарный поиск. 4 | func Binary(data []int, item int) int { 5 | low, high := 0, len(data)-1 6 | for low <= high { 7 | mid := (low + high) / 2 8 | if data[mid] == item { 9 | return mid 10 | } 11 | if data[mid] < item { 12 | low = mid + 1 13 | } else { 14 | high = mid - 1 15 | } 16 | } 17 | return -1 18 | } 19 | 20 | // Simple возвращает номер элемента в массиве или -1. Используется простой поиск. 21 | func Simple(data []int, item int) int { 22 | for i := range data { 23 | if data[i] == item { 24 | return i 25 | } 26 | } 27 | return -1 28 | } 29 | -------------------------------------------------------------------------------- /08-prof_debug/1-bench_profile/search_test.go: -------------------------------------------------------------------------------- 1 | package bsearch 2 | 3 | import ( 4 | "math/rand" 5 | "sort" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestSearches(t *testing.T) { 11 | tests := []struct { 12 | name string 13 | data []int 14 | item int 15 | want int 16 | }{ 17 | { 18 | name: "#1", 19 | data: []int{1, 2, 4, 6, 10}, 20 | item: 6, 21 | want: 3, 22 | }, 23 | { 24 | name: "#2", 25 | data: []int{1, 2, 4, 5, 6, 10}, 26 | item: 6, 27 | want: 4, 28 | }, 29 | { 30 | name: "#3", 31 | data: []int{1, 2, 4, 5, 6, 10}, 32 | item: 16, 33 | want: -1, 34 | }, 35 | { 36 | name: "#4", 37 | data: []int{1, 2, 4, 5, 6, 10}, 38 | item: 2, 39 | want: 1, 40 | }, 41 | { 42 | name: "#5", 43 | data: []int{1, 2, 4, 5, 6, 10, 100}, 44 | item: 4, 45 | want: 2, 46 | }, 47 | } 48 | for _, tt := range tests { 49 | t.Run(tt.name, func(t *testing.T) { 50 | if got := Binary(tt.data, tt.item); got != tt.want { 51 | t.Errorf("Binary() = %v, want %v", got, tt.want) 52 | } 53 | if got := Simple(tt.data, tt.item); got != tt.want { 54 | t.Errorf("Simple() = %v, want %v", got, tt.want) 55 | } 56 | }) 57 | } 58 | } 59 | 60 | func sampleData() []int { 61 | rand.Seed(time.Now().UnixNano()) 62 | var data []int 63 | for i := 0; i < 1_000_000; i++ { 64 | data = append(data, rand.Intn(1000)) 65 | } 66 | 67 | sort.Slice(data, func(i, j int) bool { return data[i] < data[j] }) 68 | return data 69 | } 70 | 71 | func BenchmarkBinary(b *testing.B) { 72 | data := sampleData() 73 | for i := 0; i < b.N; i++ { 74 | n := rand.Intn(1000) 75 | res := Binary(data, n) 76 | _ = res 77 | } 78 | } 79 | 80 | func BenchmarkSimple(b *testing.B) { 81 | data := sampleData() 82 | for i := 0; i < b.N; i++ { 83 | n := rand.Intn(1000) 84 | res := Simple(data, n) 85 | _ = res 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /08-prof_debug/2-app_profile/app-profile.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "math" 5 | "net/http" 6 | _ "net/http/pprof" 7 | ) 8 | 9 | func main() { 10 | go compute1(1_000_000, 1_000_000) 11 | go compute2(2_000_000, 2_000_000) 12 | 13 | go mem1() 14 | go mem2() 15 | 16 | http.ListenAndServe(":80", nil) 17 | } 18 | 19 | func compute1(n, m int) { 20 | for i := 0; i < n; i++ { 21 | for j := 0; j < m; j++ { 22 | _ = math.Sin(float64(i)) * math.Sin(float64(j)) 23 | } 24 | } 25 | } 26 | 27 | func compute2(n, m int) { 28 | for i := 0; i < n; i++ { 29 | for j := 0; j < m; j++ { 30 | _ = math.Cos(float64(i)) * math.Cos(float64(j)) 31 | } 32 | } 33 | } 34 | 35 | func mem1() { 36 | var arr []int32 37 | for i := 0; i < 10_000_000; i++ { 38 | arr = append(arr, int32(i)) 39 | } 40 | _ = arr 41 | select {} 42 | } 43 | 44 | func mem2() { 45 | var arr []int64 46 | for i := 20_000_000; i > 0; i-- { 47 | arr = append(arr, int64(i)) 48 | } 49 | _ = arr 50 | select {} 51 | } 52 | -------------------------------------------------------------------------------- /08-prof_debug/3-debug/two_sum.go: -------------------------------------------------------------------------------- 1 | /* 2 | Given an array of integers nums and an integer target, 3 | return indices of the two numbers such that they add up to target. 4 | You may assume that each input would have exactly one solution, 5 | and you may not use the same element twice. 6 | You can return the answer in any order. 7 | 8 | Example 1: 9 | 10 | Input: nums = [2,7,11,15], target = 9 11 | Output: [0,1] 12 | Output: Because nums[0] + nums[1] == 9, we return [0, 1]. 13 | Example 2: 14 | 15 | Input: nums = [3,2,4], target = 6 16 | Output: [1,2] 17 | */ 18 | package main 19 | 20 | import "reflect" 21 | 22 | var a int 23 | 24 | func main() { 25 | a = 10 26 | _ = a 27 | go double(a) 28 | 29 | res := twoSum([]int{2, 7, 11, 15}, 13) 30 | if !reflect.DeepEqual(res, [2]int{0, 2}) { 31 | println("неверный результат") 32 | return 33 | } 34 | println("Номера элементов:", res[0], res[1]) 35 | 36 | res = twoSum([]int{2, 10, 6, 12, 14}, 20) 37 | if !reflect.DeepEqual(res, [2]int{2, 4}) { 38 | println("неверный результат") 39 | return 40 | } 41 | println("Номера элементов:", res[0], res[1]) 42 | } 43 | 44 | func twoSum(nums []int, target int) [2]int { 45 | for i := 0; i < len(nums); i++ { 46 | for j := 0; j < len(nums); j++ { 47 | if nums[i]+nums[j] == target { 48 | return [2]int{i, j} 49 | } 50 | } 51 | } 52 | 53 | return [2]int{-1, -1} 54 | } 55 | 56 | func double(a int) int { 57 | b := a * a 58 | return b 59 | } 60 | -------------------------------------------------------------------------------- /08-prof_debug/4-trace/trace_cpu/trace.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "math" 6 | "os" 7 | "runtime/trace" 8 | "time" 9 | ) 10 | 11 | func main() { 12 | // файл, в который будет сохраняться трассировка 13 | f, err := os.Create("trace.out") 14 | if err != nil { 15 | log.Fatal(err) 16 | } 17 | // запуск трассировки и отложенное завершение 18 | trace.Start(f) 19 | defer f.Close() 20 | defer trace.Stop() 21 | 22 | go compute1() 23 | go compute1() 24 | go compute2() 25 | go compute2() 26 | 27 | time.Sleep(time.Second * 1) 28 | } 29 | 30 | func compute1() { 31 | for { 32 | _ = math.Sin(1) * math.Cos(10) 33 | } 34 | } 35 | 36 | func compute2() { 37 | for { 38 | _ = math.Tan(1) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /08-prof_debug/4-trace/trace_mem/trace.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "runtime" 7 | "runtime/trace" 8 | ) 9 | 10 | func main() { 11 | 12 | // файл, в который будет сохраняться трассировка 13 | f, err := os.Create("trace.out") 14 | if err != nil { 15 | log.Fatal(err) 16 | } 17 | 18 | // запуск трассировки и отложенное завершение 19 | trace.Start(f) 20 | defer f.Close() 21 | defer trace.Stop() 22 | 23 | nums := fillSlice() 24 | emptySlice(nums) 25 | 26 | runtime.GC() 27 | 28 | nums = fillSlice() 29 | emptySlice(nums) 30 | } 31 | 32 | func fillSlice() []int { 33 | var nums []int 34 | for i := 0; i < 100_000; i++ { // Изменить на 300_000. 35 | nums = append(nums, i) 36 | } 37 | return nums 38 | } 39 | 40 | func emptySlice(nums []int) { 41 | for i := len(nums) - 1; i >= 0; i-- { 42 | nums = append(nums[:i], nums[:i+1]...) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /09-interfaces/1-polymorph/polymorph.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "encoding/xml" 6 | "io" 7 | "log" 8 | "os" 9 | ) 10 | 11 | // serializer - абстрактный интерфейсный тип даных. 12 | // Объявляет контракт на сериализацию. 13 | type serializer interface { 14 | serialize() ([]byte, error) // контракт интерфейса 15 | } 16 | 17 | // Конкретный тип данных. 18 | type person struct { 19 | Name string 20 | Age int 21 | } 22 | 23 | // Метод, выполняющий контракт интерфейса. 24 | func (p *person) serialize() ([]byte, error) { 25 | b, err := json.Marshal(p) 26 | if err != nil { 27 | return nil, err 28 | } 29 | return b, nil 30 | } 31 | 32 | // 33 | // Другой тип, выполняющий контракт интерфейса. 34 | // 35 | 36 | type car struct { 37 | Model string 38 | Year int 39 | } 40 | 41 | func (c *car) serialize() ([]byte, error) { 42 | b, err := xml.Marshal(c) 43 | if err != nil { 44 | return nil, err 45 | } 46 | return b, nil 47 | } 48 | 49 | // Полиморфическая функция, принимающая интерфейс. 50 | func store(s serializer, w io.Writer) error { 51 | if wc2, ok := w.(io.WriteCloser); ok { 52 | defer wc2.Close() 53 | } 54 | 55 | b, err := s.serialize() 56 | if err != nil { 57 | return err 58 | } 59 | 60 | _, err = w.Write(b) 61 | return err 62 | } 63 | 64 | func main() { 65 | p := person{ 66 | Name: "Курт", 67 | Age: 27, 68 | } 69 | var s serializer = &p 70 | 71 | f, err := os.Create("./output.txt") 72 | if err != nil { 73 | log.Fatal(err) 74 | } 75 | 76 | err = store(s, f) 77 | if err != nil { 78 | log.Fatal(err) 79 | } 80 | 81 | err = store(&p, f) 82 | if err != nil { 83 | log.Fatal(err) 84 | } 85 | 86 | // *************************************************** // 87 | 88 | c := car{ 89 | Model: "Beetle", 90 | Year: 1970, 91 | } 92 | 93 | err = store(&c, os.Stdout) 94 | if err != nil { 95 | log.Fatal(err) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /09-interfaces/2-quiz/quiz.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func main() { 8 | var iface any // 0 9 | var f func() // 1 10 | var m map[string]int // 2 11 | var p *int = nil 12 | var iface2 any = p // 3 13 | cycle(iface, f, m, iface2) 14 | } 15 | 16 | func cycle(ifaces ...any) { 17 | for i, iface := range ifaces { 18 | if iface == nil { 19 | fmt.Println(i) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /09-interfaces/3-assertion/assertion.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | ) 7 | 8 | type modern interface { 9 | isModern() bool 10 | } 11 | 12 | type old interface { 13 | isOld() bool 14 | isObsolete() bool 15 | } 16 | 17 | type cassete struct { 18 | label string 19 | } 20 | 21 | func (c cassete) isModern() bool { 22 | return false 23 | } 24 | 25 | func (c cassete) isOld() bool { 26 | return true 27 | } 28 | 29 | func (c *cassete) isObsolete() bool { 30 | return false 31 | } 32 | 33 | func main() { 34 | var m modern 35 | var c cassete 36 | 37 | m = c 38 | 39 | //m.label = "Ace Of Base" - ошибка 40 | m.isModern() 41 | 42 | switch c1 := m.(type) { 43 | case cassete: 44 | c1.label = "Ace Of Base" 45 | case old: 46 | c1.isOld() 47 | } 48 | 49 | if casseteInstance, ok := m.(cassete); ok { 50 | casseteInstance.label = "Ace Of Base" 51 | log.Printf("%+v", casseteInstance) 52 | } 53 | 54 | if oldInstance, ok := m.(old); ok { 55 | fmt.Println("Old: ", oldInstance.isOld(), "\tObsolete: ", oldInstance.isObsolete()) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /09-interfaces/4-type_switch/switch.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func main() { 8 | slice := []any{10, "String", true} 9 | for _, v := range slice { 10 | switch v.(type) { 11 | case int: 12 | fmt.Println("Число:", v) 13 | case string: 14 | fmt.Println("Строка:", v) 15 | default: 16 | fmt.Println("Неизвестно:", v) 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /09-interfaces/5-important/important.go: -------------------------------------------------------------------------------- 1 | package important 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | type E struct { 9 | code int 10 | msg string 11 | } 12 | 13 | func (e *E) Error() string { 14 | return e.msg + fmt.Sprintf(": %v", e.code) 15 | } 16 | 17 | // builtin 18 | type error interface { 19 | Error() string 20 | } 21 | 22 | // io 23 | type Reader interface { 24 | Read(p []byte) (n int, err error) 25 | } 26 | 27 | // io 28 | type Writer interface { 29 | Write(p []byte) (n int, err error) 30 | } 31 | 32 | // sort 33 | type Interface interface { 34 | Len() int 35 | Less(i, j int) bool 36 | Swap(i, j int) 37 | } 38 | 39 | // fmt 40 | type Stringer interface { 41 | String() string 42 | } 43 | 44 | // net 45 | type Conn interface { 46 | Read(b []byte) (n int, err error) 47 | Write(b []byte) (n int, err error) 48 | Close() error 49 | LocalAddr() Addr 50 | RemoteAddr() Addr 51 | SetDeadline(t time.Time) error 52 | SetReadDeadline(t time.Time) error 53 | SetWriteDeadline(t time.Time) error 54 | } 55 | 56 | // net 57 | type Listener interface { 58 | Accept() (Conn, error) 59 | Close() error 60 | Addr() Addr 61 | } 62 | 63 | // net/http 64 | type Handler interface { 65 | ServeHTTP(ResponseWriter, *Request) 66 | } 67 | 68 | // net/http 69 | type ResponseWriter interface { 70 | Header() Header 71 | Write([]byte) (int, error) 72 | WriteHeader(int) 73 | } 74 | 75 | // encoding/json 76 | type Marshaler interface { 77 | MarshalJSON() ([]byte, error) 78 | } 79 | 80 | // Для целей документирования. 81 | // Реальные типы другие! 82 | type Header int 83 | type Addr int 84 | type Request int 85 | -------------------------------------------------------------------------------- /09-interfaces/6-generics/generics.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "golang.org/x/exp/slices" 7 | ) 8 | 9 | type ordered interface { 10 | int | int64 | float64 | float32 | byte | string 11 | } 12 | 13 | // min - обобщённая функция, возврающая минимальный элемент в массиве. 14 | func min[T ordered](in []T) (T, bool) { 15 | var res T 16 | if len(in) == 0 { 17 | return res, false 18 | } 19 | res = in[0] 20 | for _, v := range in { 21 | if v < res { 22 | res = v 23 | } 24 | } 25 | return res, true 26 | } 27 | 28 | func main() { 29 | ints := []int{-2, 0, 2, 3} 30 | floats := []float32{2.3, 3.4, 4.5} 31 | strings := []string{"def", "Абв", "ABC"} 32 | 33 | if val, ok := min[int](ints); ok { 34 | fmt.Printf("%v\n", val) 35 | } 36 | 37 | if val, ok := min(floats); ok { 38 | fmt.Printf("%v\n", val) 39 | } 40 | 41 | if val, ok := min(strings); ok { 42 | fmt.Printf("%v\n", val) 43 | } 44 | 45 | var slice = []int{1, 2, 3} 46 | fmt.Println(slices.Contains(slice, 2)) 47 | fmt.Println(slices.Contains(slice, 20)) 48 | } 49 | -------------------------------------------------------------------------------- /09-interfaces/7-DIP/dip.go: -------------------------------------------------------------------------------- 1 | package dip 2 | 3 | // База данных - деталь реализации. 4 | type DB []Book 5 | 6 | type Book struct { 7 | ID int 8 | Title string 9 | } 10 | 11 | // Интерфейс - часть бизнес-логики. 12 | type Storage interface { 13 | Books() []Book 14 | } 15 | 16 | func (db DB) Books() []Book { 17 | return []Book{} 18 | } 19 | 20 | // Нарушение принципа DIP. 21 | type WrongServer struct { 22 | db DB 23 | } 24 | 25 | // Выполнение принципа DIP. 26 | type GoodServer struct { 27 | db Storage 28 | } 29 | 30 | // Бизнес-логика системы. 31 | func (s *WrongServer) BusinessLogic() { 32 | // Принцип DIP нарушен, обращение к БД. 33 | books := s.db.Books() 34 | _ = books 35 | } 36 | 37 | // Бизнес-логика системы. 38 | func (s *GoodServer) BusinessLogic() { 39 | // Принцип DIP выполняется, обращение к интерфейсу. 40 | books := s.db.Books() 41 | _ = books 42 | } 43 | -------------------------------------------------------------------------------- /10-concurrency/1-goroutine/goroutine.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func printN(n int) int { 9 | fmt.Println(n) 10 | return n * 2 11 | } 12 | 13 | func main() { 14 | for i := 0; i < 10; i++ { 15 | go printN(i) // что будет выведено на экран? 16 | } 17 | 18 | time.Sleep(time.Second) 19 | } 20 | -------------------------------------------------------------------------------- /10-concurrency/10-pattern-pipeline/pipeline.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func gen(nums ...int) <-chan int { 6 | out := make(chan int) 7 | go func() { 8 | for _, n := range nums { 9 | out <- n 10 | } 11 | close(out) 12 | }() 13 | return out 14 | } 15 | 16 | func proc(in <-chan int) <-chan int { 17 | out := make(chan int) 18 | go func() { 19 | for n := range in { 20 | out <- n * n 21 | } 22 | close(out) 23 | }() 24 | return out 25 | } 26 | 27 | func proc2(in <-chan int) <-chan int { 28 | out := make(chan int) 29 | go func() { 30 | for n := range in { 31 | out <- n * n * n 32 | } 33 | close(out) 34 | }() 35 | return out 36 | } 37 | 38 | func main() { 39 | res := proc(proc2(proc2(proc(gen(1, 2, 3, 4, 5))))) 40 | 41 | for val := range res { 42 | fmt.Println(val) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /10-concurrency/11-context/context.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | ) 8 | 9 | func worker(ctx context.Context, in <-chan string, out chan<- string) { 10 | for { 11 | select { 12 | case <-ctx.Done(): 13 | fmt.Println("объект контекста вызвал завершение потока!") 14 | close(out) 15 | fmt.Println("Val:", ctx.Value("key")) 16 | return 17 | case val := <-in: 18 | fmt.Println("Обработка сообщения:", val) 19 | out <- fmt.Sprintf("Сообщение %s обработано", val) 20 | } 21 | } 22 | } 23 | 24 | func main() { 25 | data := make(chan string) 26 | result := make(chan string) 27 | ctx, cancel := context.WithTimeout(context.Background(), 700*time.Millisecond) 28 | ctx = context.WithValue(ctx, "key", "val") 29 | defer cancel() 30 | 31 | go worker(ctx, data, result) 32 | 33 | go func() { 34 | for i := 0; i < 10; i++ { 35 | if i > 8 { 36 | cancel() 37 | break 38 | } 39 | data <- fmt.Sprintf("сообщение #%d", i) 40 | time.Sleep(time.Millisecond * 500) 41 | } 42 | }() 43 | 44 | for val := range result { 45 | fmt.Println(val) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /10-concurrency/2-chan/chan.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func main() { 9 | ch := make(chan int) 10 | 11 | go func() { 12 | for i := 1; i <= 5; i++ { 13 | ch <- i 14 | time.Sleep(time.Second) 15 | } 16 | close(ch) 17 | }() 18 | 19 | for { 20 | val, ok := <-ch 21 | fmt.Println(val, ok) 22 | if !ok { 23 | break 24 | } 25 | } 26 | 27 | for val := range ch { 28 | fmt.Println(val) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /10-concurrency/3-chan_select/select.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "strconv" 7 | "time" 8 | ) 9 | 10 | func generator(ch chan<- string, num int) { 11 | for i := 0; i < 5; i++ { 12 | time.Sleep(time.Millisecond * time.Duration(rand.Intn(1000))) 13 | ch <- "Сообщение из канала №" + strconv.Itoa(num) 14 | } 15 | close(ch) 16 | } 17 | 18 | func main() { 19 | rand.Seed(time.Now().UnixNano()) 20 | ch1 := make(chan string) 21 | ch2 := make(chan string) 22 | go generator(ch1, 1) 23 | go generator(ch2, 2) 24 | 25 | OUT: 26 | for { 27 | select { 28 | case val, ok := <-ch1: 29 | if !ok { 30 | break OUT 31 | } 32 | fmt.Println(val) 33 | case val := <-ch2: 34 | fmt.Println(val) 35 | default: 36 | continue 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /10-concurrency/4-waitgroup/sync.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func printN(n int, wg *sync.WaitGroup) { 9 | defer wg.Done() 10 | fmt.Println(n) 11 | } 12 | 13 | func step1(ch chan<- string) { 14 | // ... 15 | ch <- "message from Step 1" 16 | } 17 | func step2(ch <-chan string) { 18 | fmt.Println(<-ch) 19 | // .... 20 | } 21 | 22 | func main() { 23 | const N = 10 24 | var wg sync.WaitGroup 25 | wg.Add(N) 26 | // Запуск отдельных потоков. 27 | for i := 0; i < N; i++ { 28 | go printN(i, &wg) 29 | } 30 | // Ожидание завершения потоков. 31 | wg.Wait() 32 | 33 | // Синхронизация двух потоков с помощью 34 | // сообщений в канале. 35 | var ch = make(chan string) 36 | go step1(ch) 37 | step2(ch) 38 | } 39 | -------------------------------------------------------------------------------- /10-concurrency/5-shared_mem/shared.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | // Критическая секция. 9 | var mu sync.Mutex 10 | var counter int 11 | 12 | // inc прибавляет x к sum 13 | func inc(wg *sync.WaitGroup) { 14 | defer wg.Done() 15 | 16 | for i := 0; i < 10_000; i++ { 17 | mu.Lock() 18 | counter++ 19 | mu.Unlock() 20 | } 21 | } 22 | 23 | func main() { 24 | var wg sync.WaitGroup 25 | wg.Add(2) 26 | 27 | go inc(&wg) 28 | go inc(&wg) 29 | 30 | wg.Wait() 31 | 32 | fmt.Println(counter) // Что будет на экране? 33 | } 34 | -------------------------------------------------------------------------------- /10-concurrency/6-common_errors/errors.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func error1() { 9 | // Ошибка №1. Доступ к итератору цикла через замыкание. 10 | for i := 0; i < 10; i++ { 11 | go func() { 12 | fmt.Println(i) 13 | }() 14 | } 15 | time.Sleep(1) 16 | } 17 | 18 | func error2() { 19 | for i := 0; i < 10; i++ { 20 | go func(i int) { 21 | fmt.Println(i) 22 | }(i) 23 | } 24 | // Ошибка №2. Использование таймера для синхронизации. 25 | time.Sleep(1) 26 | } 27 | 28 | func error3() { 29 | f := func(i int) { 30 | fmt.Println(i) 31 | } 32 | // Ошибка №3. Отсутствие контроля за завершением потоков. 33 | for i := 0; i < 10; i++ { 34 | go f(i) 35 | } 36 | } 37 | 38 | func main() { 39 | //error1() 40 | //error2() 41 | //error3() 42 | } 43 | -------------------------------------------------------------------------------- /10-concurrency/7-atomic/atomic.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "sync/atomic" 7 | ) 8 | 9 | func main() { 10 | var c atomic.Int64 11 | 12 | N := 100_000 13 | 14 | var wg sync.WaitGroup 15 | wg.Add(N) 16 | for i := 0; i < N; i++ { 17 | go func() { 18 | c.Add(1) 19 | wg.Done() 20 | }() 21 | } 22 | wg.Wait() 23 | 24 | fmt.Println("Счётчик:", c.Load()) 25 | } 26 | -------------------------------------------------------------------------------- /10-concurrency/8-pattern-fan-out/fan-out.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "sync" 7 | ) 8 | 9 | func processor(in <-chan int, out chan<- int) { 10 | for val := range in { 11 | out <- val * val 12 | } 13 | } 14 | 15 | func main() { 16 | src := make(chan int) 17 | res := make(chan int) 18 | 19 | n := runtime.NumCPU() 20 | // Запуск рабочих потоков. 21 | var wg sync.WaitGroup 22 | wg.Add(n) 23 | for i := 0; i < n; i++ { 24 | go func() { 25 | defer wg.Done() 26 | processor(src, res) 27 | }() 28 | } 29 | 30 | // Поток с заданиями. 31 | go func() { 32 | for i := 0; i < 10; i++ { 33 | src <- i 34 | } 35 | close(src) 36 | }() 37 | 38 | // Поток с обработкой результатов. 39 | go func() { 40 | for val := range res { 41 | fmt.Println(val) 42 | } 43 | }() 44 | 45 | wg.Wait() 46 | 47 | close(res) 48 | } 49 | -------------------------------------------------------------------------------- /10-concurrency/9-pattern-fan-in/fan-in.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func worker(n int) chan int { 9 | ch := make(chan int) 10 | go func() { 11 | defer close(ch) 12 | ch <- n * n 13 | }() 14 | return ch 15 | } 16 | 17 | func fanIn[T any](channels ...<-chan T) <-chan T { 18 | ch := make(chan T) 19 | var wg sync.WaitGroup 20 | wg.Add(len(channels)) 21 | 22 | for _, c := range channels { 23 | go func(in <-chan T) { 24 | defer wg.Done() 25 | for i := range in { 26 | ch <- i 27 | } 28 | }(c) 29 | } 30 | 31 | go func() { 32 | wg.Wait() 33 | close(ch) 34 | }() 35 | 36 | return ch 37 | } 38 | 39 | func main() { 40 | var chans []<-chan int 41 | for i := 0; i < 10; i++ { 42 | chans = append(chans, worker(i)) 43 | } 44 | 45 | ch := fanIn(chans...) 46 | for val := range ch { 47 | fmt.Println(val) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /11-network/1-daytime/client/daytime-client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "log" 7 | "net" 8 | ) 9 | 10 | func main() { 11 | conn, err := net.Dial("tcp4", "localhost:13") 12 | if err != nil { 13 | log.Fatal(err) 14 | } 15 | 16 | msg, err := io.ReadAll(conn) 17 | if err != nil { 18 | log.Fatal(err) 19 | } 20 | 21 | fmt.Println("Ответ от сервера:", string(msg)) 22 | } 23 | -------------------------------------------------------------------------------- /11-network/1-daytime/server/daytime-server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Сервер текущего времени в соответсвии с RFC 867. 4 | 5 | import ( 6 | "io" 7 | "log" 8 | "net" 9 | "time" 10 | ) 11 | 12 | // обработчик подключения 13 | func handler(conn io.ReadWriteCloser) { 14 | daytime := time.Now().Format(time.RubyDate) 15 | conn.Write([]byte(daytime)) 16 | conn.Close() 17 | } 18 | 19 | func main() { 20 | // регистрация сетевой службы на всех сетевых интерфейсах на порту 13 21 | listener, err := net.Listen("tcp4", "0.0.0.0:13") 22 | if err != nil { 23 | log.Fatal(err) 24 | } 25 | defer listener.Close() 26 | 27 | // цикл обработки клиентских подключений 28 | for { 29 | conn, err := listener.Accept() 30 | if err != nil { 31 | log.Fatal(err) 32 | } 33 | 34 | handler(conn) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /11-network/2-echo/echo.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Эхо-сервер. Без цензуры. 4 | 5 | import ( 6 | "bufio" 7 | "fmt" 8 | "log" 9 | "net" 10 | ) 11 | 12 | // обработчик подключения 13 | func handler(conn net.Conn) { 14 | defer conn.Close() 15 | defer fmt.Println("Connection Closed") 16 | 17 | r := bufio.NewReader(conn) 18 | for { 19 | msg, _, err := r.ReadLine() 20 | if err != nil { 21 | return 22 | } 23 | 24 | msg = append(msg, '\n') 25 | 26 | _, err = conn.Write(msg) 27 | if err != nil { 28 | return 29 | } 30 | } 31 | } 32 | 33 | func main() { 34 | // регистрация сетевой службы 35 | listener, err := net.Listen("tcp4", ":12345") 36 | if err != nil { 37 | log.Fatal(err) 38 | } 39 | defer listener.Close() 40 | 41 | fmt.Println("Сервер слушает на порту: 12345") 42 | 43 | // цикл обработки клиентских подключений 44 | for { 45 | conn, err := listener.Accept() 46 | if err != nil { 47 | log.Fatal(err) 48 | } 49 | go handler(conn) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /11-network/3-deadline/deadline.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Эхо-сервер. Без цензуры. 4 | 5 | import ( 6 | "bufio" 7 | "fmt" 8 | "log" 9 | "net" 10 | "time" 11 | ) 12 | 13 | // обработчик подключения 14 | func handler(conn net.Conn) { 15 | defer conn.Close() 16 | defer fmt.Println("Conn closed") 17 | 18 | conn.SetDeadline(time.Now().Add(time.Second * 5)) 19 | 20 | r := bufio.NewReader(conn) 21 | for { 22 | msg, _, err := r.ReadLine() 23 | if err != nil { 24 | return 25 | } 26 | 27 | fmt.Println(string(msg)) 28 | _, err = conn.Write(msg) 29 | if err != nil { 30 | return 31 | } 32 | 33 | conn.SetDeadline(time.Now().Add(time.Second * 5)) 34 | } 35 | } 36 | 37 | func main() { 38 | // регистрация сетевой службы 39 | listener, err := net.Listen("tcp4", ":12345") 40 | if err != nil { 41 | log.Fatal(err) 42 | } 43 | 44 | // цикл обработки клиентских подключений 45 | for { 46 | conn, err := listener.Accept() 47 | if err != nil { 48 | log.Fatal(err) 49 | } 50 | fmt.Println("Conn established") 51 | 52 | go handler(conn) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /11-network/4-testing/timeserver.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "log" 6 | "net" 7 | "strings" 8 | "time" 9 | ) 10 | 11 | // Сетевой адрес. 12 | // 13 | // Служба будет слушать запросы на всех IP-адресах 14 | // компьютера на порту 12345. 15 | // Нпример, 127.0.0.1:12345 16 | const addr = "0.0.0.0:12345" 17 | 18 | // Протокол сетевой службы. 19 | const proto = "tcp4" 20 | 21 | func main() { 22 | // Запуск сетевой службы про протоколу TCP 23 | // на порту 12345. 24 | listener, err := net.Listen(proto, addr) 25 | if err != nil { 26 | log.Fatal(err) 27 | } 28 | // Подключения обрабатываются в бесконечном цикле. 29 | // Иначе после обслуживания первого подключения сервер 30 | //завершит работу. 31 | for { 32 | // Принимаем подключение. 33 | conn, err := listener.Accept() 34 | if err != nil { 35 | log.Fatal(err) 36 | } 37 | // Вызов обработчика подключения. 38 | go handleConn(conn) 39 | } 40 | } 41 | 42 | // Обработчик. Вызывается для каждого соединения. 43 | func handleConn(conn net.Conn) { 44 | // Чтение сообщения от клиента. 45 | reader := bufio.NewReader(conn) 46 | b, err := reader.ReadBytes('\n') 47 | if err != nil { 48 | log.Println(err) 49 | return 50 | } 51 | 52 | // Удаление символов конца строки. 53 | msg := strings.TrimSuffix(string(b), "\n") 54 | msg = strings.TrimSuffix(msg, "\r") 55 | 56 | // Если получили "time" - пишем время в соединение. 57 | if msg == "time" { 58 | conn.Write([]byte(time.Now().String() + "\n")) 59 | } 60 | 61 | // Закрытие соединения. 62 | conn.Close() 63 | } 64 | -------------------------------------------------------------------------------- /11-network/4-testing/timeserver_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "log" 6 | "net" 7 | "sync" 8 | "testing" 9 | ) 10 | 11 | func Test_handleConn(t *testing.T) { 12 | // Имитация полнодуплексного 13 | // сетевого соединения. 14 | srv, cl := net.Pipe() 15 | 16 | // Мы должны дождаться 17 | // завершения всех потоков. 18 | var wg sync.WaitGroup 19 | wg.Add(1) 20 | 21 | // Обработчик подключения запускается 22 | // в отдельном потоке. 23 | go func() { 24 | // Обработчику передается один из 25 | // концов виртуальной трубы. 26 | handleConn(srv) 27 | srv.Close() 28 | wg.Done() 29 | }() 30 | 31 | // В основном потоке теста клиент отправляет 32 | // сообщения в трубу. 33 | // Эти сообщения сервер прочитает в соседнем потоке. 34 | _, err := cl.Write([]byte("time\n")) 35 | if err != nil { 36 | log.Fatal(err) 37 | } 38 | 39 | // Чтенеие ответа от сервера. 40 | reader := bufio.NewReader(cl) 41 | b, err := reader.ReadBytes('\n') 42 | if err != nil { 43 | log.Fatal(err) 44 | } 45 | 46 | // Проверка ответа. 47 | if len(b) < 10 { 48 | t.Error("время не получено") 49 | } 50 | wg.Wait() 51 | cl.Close() 52 | } 53 | -------------------------------------------------------------------------------- /12-web-apps/1-http/client/simple-client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "log" 8 | "net" 9 | "net/http" 10 | "time" 11 | ) 12 | 13 | func main() { 14 | url := "http://localhost:8080" 15 | 16 | // Самый простой вариант запроса. 17 | resp, err := http.Get(url) 18 | if err != nil { 19 | log.Fatal(err) 20 | } 21 | 22 | body, err := io.ReadAll(resp.Body) 23 | if err != nil { 24 | log.Fatal(err) 25 | } 26 | 27 | fmt.Println(string(body)) 28 | 29 | // Запрос с разными настройками. 30 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 31 | defer cancel() 32 | req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) 33 | if err != nil { 34 | log.Fatal(err) 35 | } 36 | 37 | transport := &http.Transport{ 38 | Proxy: http.ProxyFromEnvironment, 39 | DialContext: defaultTransportDialContext(&net.Dialer{ 40 | Timeout: 30 * time.Second, 41 | KeepAlive: 30 * time.Second, 42 | }), 43 | ForceAttemptHTTP2: true, 44 | MaxIdleConns: 100, 45 | IdleConnTimeout: 90 * time.Second, 46 | TLSHandshakeTimeout: 10 * time.Second, 47 | ExpectContinueTimeout: 1 * time.Second, 48 | } 49 | 50 | client := http.Client{ 51 | Transport: transport, 52 | Timeout: 5 * time.Second, 53 | } 54 | 55 | resp, err = client.Do(req) 56 | if err != nil { 57 | log.Fatal(err) 58 | } 59 | 60 | body, err = io.ReadAll(resp.Body) 61 | if err != nil { 62 | log.Fatal(err) 63 | } 64 | 65 | fmt.Println(string(body)) 66 | } 67 | 68 | func defaultTransportDialContext(dialer *net.Dialer) func(context.Context, string, string) (net.Conn, error) { 69 | return dialer.DialContext 70 | } 71 | -------------------------------------------------------------------------------- /12-web-apps/1-http/server/simple-server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | func main() { 8 | // регистрация обработчика для URL `/` в маршрутизаторе по умолчанию 9 | http.HandleFunc("/", mainHandler) 10 | 11 | // старт HTTP-сервера на порту 8080 протокола TCP с маршрутизатором запросов по умолчанию 12 | http.ListenAndServe(":8080", nil) 13 | } 14 | 15 | // HTTP-обработчик 16 | func mainHandler(w http.ResponseWriter, r *http.Request) { 17 | w.Write([]byte(`

Go Simple Web App

`)) 18 | } 19 | -------------------------------------------------------------------------------- /12-web-apps/2-custom_server/custom-server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net" 6 | "net/http" 7 | "time" 8 | ) 9 | 10 | func main() { 11 | const addr = ":8080" 12 | 13 | // Параметры веб-сервера. 14 | srv := &http.Server{ 15 | ReadTimeout: 10 * time.Second, 16 | WriteTimeout: 20 * time.Second, 17 | Handler: nil, 18 | Addr: addr, 19 | } 20 | 21 | // Старт сетевой службы веб-сервера. 22 | listener, err := net.Listen("tcp4", addr) 23 | if err != nil { 24 | log.Fatal(err) 25 | } 26 | 27 | // Регистрация обработчика для URL `/` в маршрутизаторе по умолчанию. 28 | http.HandleFunc("/", mainHandler) 29 | 30 | // Старт самого веб-сервера. 31 | log.Fatal(srv.Serve(listener)) 32 | //log.Fatal(srv.ServeTLS(listener, "cert.crt", "cert.key")) 33 | } 34 | 35 | // HTTP-обработчик 36 | func mainHandler(w http.ResponseWriter, r *http.Request) { 37 | w.Write([]byte(`

Go Simple Web App

`)) 38 | } 39 | -------------------------------------------------------------------------------- /12-web-apps/3-gorilla_mux/gorilla.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/gorilla/mux" 8 | ) 9 | 10 | func main() { 11 | // Сторонний маршрутизатор из пакета Gorilla. 12 | mux := mux.NewRouter() 13 | 14 | // Регистрация обработчика для URL `/` в маршрутизаторе по умолчанию. 15 | mux.HandleFunc("/{name}", mainHandler).Methods(http.MethodGet) 16 | 17 | // Старт HTTP-сервера на порту 8080 протокола TCP с маршрутизатором запросов "mux". 18 | http.ListenAndServe(":8080", mux) 19 | } 20 | 21 | // HTTP-обработчик. 22 | func mainHandler(w http.ResponseWriter, r *http.Request) { 23 | vars := mux.Vars(r) 24 | w.WriteHeader(http.StatusOK) 25 | fmt.Fprintf(w, "

Hi, %v

", vars["name"]) 26 | } 27 | -------------------------------------------------------------------------------- /12-web-apps/4-template/template.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "html/template" 6 | "net/http" 7 | 8 | "github.com/gorilla/mux" 9 | ) 10 | 11 | func main() { 12 | mux := mux.NewRouter() 13 | 14 | mux.Use(exampleMiddleware) 15 | mux.Use(exampleMiddleware2) 16 | 17 | mux.HandleFunc("/{first}/{last}", mainHandler).Methods(http.MethodGet) 18 | 19 | http.ListenAndServe(":8080", mux) 20 | } 21 | 22 | func exampleMiddleware(next http.Handler) http.Handler { 23 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 24 | fmt.Printf("Middleware: URL: %v\n", r.RequestURI) 25 | next.ServeHTTP(w, r) 26 | }) 27 | } 28 | 29 | func exampleMiddleware2(next http.Handler) http.Handler { 30 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 31 | fmt.Printf("Middleware2: URL: %v\n", r.RequestURI) 32 | next.ServeHTTP(w, r) 33 | }) 34 | } 35 | 36 | // HTTP-обработчик, возвращающий HTML-страницу. 37 | func mainHandler(w http.ResponseWriter, r *http.Request) { 38 | vars := mux.Vars(r) 39 | 40 | t := template.New("main") 41 | 42 | t, err := t.Parse("

Hi, {{.}}

") 43 | if err != nil { 44 | http.Error(w, "ошибка при обработке шаблона", http.StatusInternalServerError) 45 | return 46 | } 47 | 48 | t.Execute(w, vars["first"]+" "+vars["last"]) 49 | } 50 | -------------------------------------------------------------------------------- /12-web-apps/5-testing/testing.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/gorilla/mux" 8 | ) 9 | 10 | // сторонний маршрутизатор из пакета Gorilla 11 | var r *mux.Router 12 | 13 | func main() { 14 | r = mux.NewRouter() 15 | endpoints(r) 16 | http.ListenAndServe(":8080", r) 17 | } 18 | 19 | func endpoints(r *mux.Router) { 20 | // Регистрация обработчика для URL `/` в маршрутизаторе по умолчанию. 21 | r.HandleFunc("/{name}", mainHandler).Methods(http.MethodGet) 22 | } 23 | 24 | // HTTP-обработчик. 25 | func mainHandler(w http.ResponseWriter, r *http.Request) { 26 | vars := mux.Vars(r) 27 | w.WriteHeader(http.StatusOK) 28 | fmt.Fprintf(w, "

Hi, %v

", vars["name"]) 29 | } 30 | -------------------------------------------------------------------------------- /12-web-apps/5-testing/testing_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "net/http" 7 | "net/http/httptest" 8 | "strings" 9 | "testing" 10 | 11 | "github.com/gorilla/mux" 12 | ) 13 | 14 | var testMux *mux.Router 15 | 16 | func TestMain(m *testing.M) { 17 | testMux = mux.NewRouter() 18 | endpoints(testMux) 19 | m.Run() 20 | } 21 | 22 | func Test_mainHandler(t *testing.T) { 23 | data := []int{} 24 | payload, _ := json.Marshal(data) 25 | 26 | // Создаём HTTP=запрос. 27 | req := httptest.NewRequest(http.MethodPost, "/Name", bytes.NewBuffer(payload)) 28 | req.Header.Add("Сontent-type", "plain/text") 29 | 30 | // Объект для записи ответа HTTP-сервера. 31 | rr := httptest.NewRecorder() 32 | 33 | // Вызов маршрутизатора и обслуживание запроса. 34 | testMux.ServeHTTP(rr, req) 35 | 36 | // Анализ ответа сервера (неверный метод HTTP). 37 | if rr.Code != http.StatusMethodNotAllowed { 38 | t.Errorf("код неверен: получили %d, а хотели %d", rr.Code, http.StatusOK) 39 | } 40 | 41 | t.Log("Response: ", rr.Body) 42 | 43 | //========================================================= 44 | 45 | req = httptest.NewRequest(http.MethodGet, "/Name", nil) 46 | req.Header.Add("Сontent-type", "plain/text") 47 | 48 | rr = httptest.NewRecorder() 49 | 50 | testMux.ServeHTTP(rr, req) 51 | 52 | if rr.Code != http.StatusOK { 53 | t.Errorf("код неверен: получили %d, а хотели %d", rr.Code, http.StatusOK) 54 | } 55 | body := rr.Body.String() 56 | if !strings.Contains(body, "Name") { 57 | t.Fatal(body) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /12-web-apps/hw/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/DmitriyVTitov/size" 7 | ) 8 | 9 | type example struct { 10 | a []int 11 | b bool 12 | c int32 13 | d string 14 | } 15 | 16 | func main() { 17 | ex := example{ 18 | a: []int{1, 2, 3}, // ? 19 | b: true, // ? 20 | d: "1234", // ? 21 | } // ? 22 | fmt.Println("Размер в байтах для ex:", size.Of(ex)) 23 | // Как получается результат? 24 | 25 | ex1 := example{ 26 | a: []int{1, 2, 3}, // ? 27 | b: true, // ? 28 | d: "1234", // ? 29 | c: 100, 30 | } // ? 31 | fmt.Println("Размер в байтах для ex1:", size.Of(ex1)) 32 | // Как получается результат? 33 | } 34 | -------------------------------------------------------------------------------- /13-api/1-api/api.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | 7 | "github.com/gorilla/mux" 8 | ) 9 | 10 | // API предоставляет интерфейс программного взаимодействия. 11 | type API struct { 12 | router *mux.Router 13 | } 14 | 15 | // Endpoints регистрирует конечные точки API. 16 | func (api *API) Endpoints() { 17 | api.router.Use(logMiddleware) 18 | api.router.Use(headersMiddleware) 19 | 20 | api.router.HandleFunc("/api/v1/books", api.books).Methods(http.MethodGet) 21 | api.router.HandleFunc("/api/v1/books", api.newBook).Methods(http.MethodPost) 22 | api.router.HandleFunc("/api/v1/books/{id}", api.deleteBook).Methods(http.MethodDelete) 23 | } 24 | 25 | func (api *API) books(w http.ResponseWriter, r *http.Request) { 26 | err := json.NewEncoder(w).Encode(books) 27 | if err != nil { 28 | http.Error(w, err.Error(), http.StatusInternalServerError) 29 | return 30 | } 31 | } 32 | 33 | func (api *API) newBook(w http.ResponseWriter, r *http.Request) { 34 | var b book 35 | err := json.NewDecoder(r.Body).Decode(&b) 36 | if err != nil { 37 | http.Error(w, err.Error(), http.StatusInternalServerError) 38 | return 39 | } 40 | books = append(books, b) 41 | } 42 | 43 | func (api *API) deleteBook(w http.ResponseWriter, r *http.Request) { 44 | } 45 | -------------------------------------------------------------------------------- /13-api/1-api/api_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "net/http" 7 | "net/http/httptest" 8 | "os" 9 | "testing" 10 | 11 | "github.com/gorilla/mux" 12 | ) 13 | 14 | var api *API 15 | 16 | func TestMain(m *testing.M) { 17 | api = new(API) 18 | api.router = mux.NewRouter() 19 | api.Endpoints() 20 | os.Exit(m.Run()) 21 | } 22 | 23 | func TestAPI_newBook(t *testing.T) { 24 | want := len(books) + 1 // что здесь плохо? 25 | 26 | data := book{ 27 | Name: "1984", 28 | Author: "George Orwell", 29 | } 30 | payload, _ := json.Marshal(data) 31 | 32 | req := httptest.NewRequest(http.MethodPost, "/api/v1/books", bytes.NewBuffer(payload)) 33 | 34 | rr := httptest.NewRecorder() 35 | 36 | api.router.ServeHTTP(rr, req) 37 | if rr.Code != http.StatusOK { 38 | t.Errorf("код неверен: получили %d, а хотели %d", rr.Code, http.StatusOK) 39 | } 40 | t.Log("Response: ", rr.Body) 41 | 42 | got := len(books) 43 | 44 | // что плохо в таком сравнении? как сделать лучше? 45 | if got != want { 46 | t.Fatal("книга не добавлена") 47 | } 48 | } 49 | 50 | func TestAPI_books(t *testing.T) { 51 | req := httptest.NewRequest(http.MethodGet, "/api/v1/books", nil) 52 | rr := httptest.NewRecorder() 53 | api.router.ServeHTTP(rr, req) 54 | if !(rr.Code == http.StatusOK) { 55 | t.Errorf("код неверен: получили %d, а хотели %d", rr.Code, http.StatusOK) 56 | } 57 | t.Log("Response: ", rr.Body) 58 | } 59 | -------------------------------------------------------------------------------- /13-api/1-api/middleware.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | ) 7 | 8 | func logMiddleware(next http.Handler) http.Handler { 9 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 10 | log.Println(r.Method, r.RemoteAddr, r.RequestURI) 11 | 12 | next.ServeHTTP(w, r) 13 | }) 14 | } 15 | 16 | func headersMiddleware(next http.Handler) http.Handler { 17 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 18 | w.Header().Set("Content-Type", "application/json; charset=utf-8") 19 | 20 | next.ServeHTTP(w, r) 21 | }) 22 | } 23 | -------------------------------------------------------------------------------- /13-api/1-api/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | 7 | "github.com/gorilla/mux" 8 | ) 9 | 10 | type server struct { 11 | api *API 12 | router *mux.Router 13 | } 14 | 15 | func main() { 16 | srv := new(server) 17 | srv.router = mux.NewRouter() 18 | srv.api = &API{router: srv.router} 19 | srv.api.Endpoints() 20 | 21 | log.Fatal(http.ListenAndServe(":8081", srv.router)) 22 | } 23 | 24 | type book struct { 25 | Name string 26 | Author string 27 | } 28 | 29 | var books = []book{ 30 | { 31 | Name: "The Lord Of The Rings", 32 | Author: "J.R.R. Tolkien", 33 | }, 34 | } 35 | -------------------------------------------------------------------------------- /13-api/2-api/cmd/server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "go-core-4/13-api/2-api/pkg/api" 7 | ) 8 | 9 | type server struct { 10 | api *api.API 11 | } 12 | 13 | func main() { 14 | srv := new(server) 15 | 16 | srv.api = api.New() 17 | 18 | http.ListenAndServe(":8082", srv.api.Router()) 19 | } 20 | -------------------------------------------------------------------------------- /13-api/2-api/pkg/api/api.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | 7 | "github.com/gorilla/mux" 8 | "github.com/gorilla/sessions" 9 | "github.com/rs/zerolog" 10 | ) 11 | 12 | // API предоставляет интерфейс программного взаимодействия. 13 | type API struct { 14 | router *mux.Router 15 | store *sessions.CookieStore 16 | 17 | log *zerolog.Logger 18 | } 19 | 20 | // New создаёт объект API. 21 | func New() *API { 22 | api := API{ 23 | router: mux.NewRouter(), 24 | store: sessions.NewCookieStore([]byte("secret_password")), 25 | } 26 | 27 | api.endpoints() 28 | 29 | return &api 30 | } 31 | 32 | func (api *API) Router() *mux.Router { 33 | return api.router 34 | } 35 | 36 | // endpoints регистрирует конечные точки API. 37 | func (api *API) endpoints() { 38 | api.router.Use(requestIDMiddleware) 39 | api.router.Use(logMiddleware) 40 | api.router.Use(api.jwtMiddleware) 41 | //api.router.Use(api.sessionsMiddleware) 42 | 43 | api.router.HandleFunc("/api/v1/authSession", api.authSession).Methods(http.MethodPost, http.MethodOptions) 44 | api.router.HandleFunc("/api/v1/authJWT", api.authJWT).Methods(http.MethodPost, http.MethodOptions) 45 | 46 | api.router.HandleFunc("/api/v1/books", api.books).Methods(http.MethodGet, http.MethodOptions) 47 | api.router.HandleFunc("/api/v1/newBook", api.newBook).Methods(http.MethodPost, http.MethodOptions) 48 | 49 | rr, _ := http.NewRequestWithContext(context.Background(), "GET", "https://yandex.ru", nil) 50 | 51 | http.DefaultClient.Do(rr) 52 | } 53 | -------------------------------------------------------------------------------- /13-api/2-api/pkg/api/api_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | ) 7 | 8 | var api *API 9 | 10 | func TestMain(m *testing.M) { 11 | api = New() 12 | os.Exit(m.Run()) 13 | } 14 | -------------------------------------------------------------------------------- /13-api/2-api/pkg/api/books.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "math/rand" 7 | "net/http" 8 | "time" 9 | ) 10 | 11 | func (api *API) books(w http.ResponseWriter, r *http.Request) { 12 | err := json.NewEncoder(w).Encode(books) 13 | if err != nil { 14 | http.Error(w, err.Error(), http.StatusInternalServerError) 15 | return 16 | } 17 | } 18 | 19 | func (api *API) newBook(w http.ResponseWriter, r *http.Request) { 20 | // контекст с таймаутом 21 | timeout, cancel := context.WithTimeout(r.Context(), time.Second*10) 22 | 23 | // освобождение ресурсов, если функция завершится раньше 24 | defer cancel() 25 | 26 | // производный контекст с ключом (№ запроса) 27 | ctx := context.WithValue(timeout, "requestID", rand.Intn(1_000_000_000)) 28 | 29 | // длительная операция, принимающая контекст 30 | //data := getLotsOfDataFromDatabase(ctx) 31 | _ = ctx 32 | 33 | var b book 34 | err := json.NewDecoder(r.Body).Decode(&b) 35 | if err != nil { 36 | http.Error(w, err.Error(), http.StatusInternalServerError) 37 | return 38 | } 39 | books = append(books, b) 40 | } 41 | 42 | type book struct { 43 | Name string 44 | Author string 45 | } 46 | 47 | var books = []book{ 48 | { 49 | Name: "The Lord Of The Rings", 50 | Author: "J.R.R. Tolkien", 51 | }, 52 | } 53 | -------------------------------------------------------------------------------- /13-api/2-api/pkg/api/books_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "net/http" 7 | "net/http/httptest" 8 | "testing" 9 | ) 10 | 11 | func TestAPI_newBook(t *testing.T) { 12 | want := len(books) + 1 // что здесь плохо? 13 | data := book{ 14 | Name: "1984", 15 | Author: "George Orwell", 16 | } 17 | 18 | payload, _ := json.Marshal(data) 19 | req := httptest.NewRequest(http.MethodPost, "/api/v1/newBook", bytes.NewBuffer(payload)) 20 | 21 | rr := httptest.NewRecorder() 22 | 23 | api.router.ServeHTTP(rr, req) 24 | if rr.Code != http.StatusOK { 25 | t.Errorf("код неверен: получили %d, а хотели %d", rr.Code, http.StatusOK) 26 | } 27 | 28 | t.Log("Response: ", rr.Body) 29 | 30 | got := len(books) 31 | // что плохо в таком сравнении? как сделать лучше? 32 | if got != want { 33 | t.Fatal("книга не добавлена") 34 | } 35 | } 36 | 37 | func TestAPI_books(t *testing.T) { 38 | req := httptest.NewRequest(http.MethodGet, "/api/v1/books", nil) 39 | rr := httptest.NewRecorder() 40 | api.router.ServeHTTP(rr, req) 41 | if !(rr.Code == http.StatusOK) { 42 | t.Errorf("код неверен: получили %d, а хотели %d", rr.Code, http.StatusOK) 43 | } 44 | t.Log("Response: ", rr.Body) 45 | } 46 | -------------------------------------------------------------------------------- /13-api/2-api/pkg/api/jwt.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "encoding/json" 5 | "io" 6 | "net/http" 7 | "time" 8 | 9 | jwt "github.com/dgrijalva/jwt-go" 10 | ) 11 | 12 | func (api *API) authJWT(w http.ResponseWriter, r *http.Request) { 13 | body, err := io.ReadAll(r.Body) 14 | if err != nil { 15 | http.Error(w, err.Error(), http.StatusInternalServerError) 16 | return 17 | } 18 | var auth authInfo 19 | err = json.Unmarshal(body, &auth) 20 | if err != nil { 21 | http.Error(w, err.Error(), http.StatusInternalServerError) 22 | return 23 | } 24 | 25 | if auth.Usr == "Usr" && auth.Pwd == "Pwd" { 26 | token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ 27 | "usr": auth.Usr, 28 | "nbf": time.Now().Unix(), 29 | }) 30 | 31 | // Sign and get the complete encoded token as a string using the secret 32 | tokenString, err := token.SignedString([]byte("secret-password")) 33 | if err != nil { 34 | http.Error(w, err.Error(), http.StatusInternalServerError) 35 | return 36 | } 37 | w.Write([]byte(tokenString)) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /13-api/2-api/pkg/api/jwt_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "net/http" 7 | "net/http/httptest" 8 | "testing" 9 | ) 10 | 11 | func TestAPI_authJWT(t *testing.T) { 12 | data := authInfo{ 13 | Usr: "Usr", 14 | Pwd: "Pwd", 15 | } 16 | payload, _ := json.Marshal(data) 17 | req := httptest.NewRequest(http.MethodPost, "/api/v1/authJWT", bytes.NewBuffer(payload)) 18 | rr := httptest.NewRecorder() 19 | api.router.ServeHTTP(rr, req) 20 | if rr.Code != http.StatusOK { 21 | t.Errorf("код неверен: получили %d, а хотели %d", rr.Code, http.StatusOK) 22 | } 23 | t.Log("Response: ", rr.Body) 24 | } 25 | -------------------------------------------------------------------------------- /13-api/2-api/pkg/api/middleware.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "math/rand" 8 | "net/http" 9 | "net/http/httputil" 10 | "strings" 11 | 12 | jwt "github.com/dgrijalva/jwt-go" 13 | ) 14 | 15 | // проверка валидности сеанса 16 | func (api *API) sessionsMiddleware(next http.Handler) http.Handler { 17 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 18 | session, _ := api.store.Get(r, "session-cookie") 19 | if auth, ok := session.Values["authenticated"].(bool); !ok || !auth { 20 | http.Error(w, "доступ запрещён", http.StatusForbidden) 21 | return 22 | } 23 | next.ServeHTTP(w, r) 24 | }) 25 | } 26 | 27 | // проверка валидности JWT 28 | func (api *API) jwtMiddleware(next http.Handler) http.Handler { 29 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 30 | 31 | tokenHeader := r.Header.Get("Authorization") 32 | if tokenHeader == "" { 33 | next.ServeHTTP(w, r) 34 | return 35 | } 36 | 37 | splitted := strings.Split(tokenHeader, " ") 38 | if len(splitted) != 2 { 39 | w.WriteHeader(http.StatusBadRequest) 40 | return 41 | } 42 | 43 | tokenString := splitted[1] 44 | 45 | token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { 46 | return []byte("secret-password"), nil 47 | }) 48 | if err != nil { 49 | w.WriteHeader(http.StatusBadRequest) 50 | return 51 | } 52 | 53 | if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { 54 | fmt.Printf("Данные токена JWT: %+v\n", claims) 55 | } 56 | 57 | next.ServeHTTP(w, r) 58 | }) 59 | } 60 | 61 | // присвоение номера запросу 62 | func requestIDMiddleware(next http.Handler) http.Handler { 63 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 64 | ctx := context.WithValue(r.Context(), "request_id", rand.Intn(1_000_000)) 65 | newR := r.WithContext(ctx) 66 | b, _ := httputil.DumpRequest(newR, true) 67 | fmt.Printf("%+v", string(b)) 68 | 69 | next.ServeHTTP(w, newR) 70 | }) 71 | } 72 | 73 | // логирование запросов к API 74 | func logMiddleware(next http.Handler) http.Handler { 75 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 76 | id := r.Context().Value("request_id").(int) 77 | log.Println(r.Method, r.RemoteAddr, r.RequestURI, id) 78 | next.ServeHTTP(w, r) 79 | }) 80 | } 81 | -------------------------------------------------------------------------------- /13-api/2-api/pkg/api/middleware_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "io/ioutil" 7 | "net/http" 8 | "net/http/httptest" 9 | "testing" 10 | ) 11 | 12 | func TestAPI_jwtMiddleware(t *testing.T) { 13 | data := authInfo{ 14 | Usr: "Usr", 15 | Pwd: "Pwd", 16 | } 17 | payload, _ := json.Marshal(data) 18 | req := httptest.NewRequest(http.MethodPost, "/api/v1/authJWT", bytes.NewBuffer(payload)) 19 | rr := httptest.NewRecorder() 20 | api.router.ServeHTTP(rr, req) 21 | if rr.Code != http.StatusOK { 22 | t.Errorf("код неверен: получили %d, а хотели %d", rr.Code, http.StatusOK) 23 | } 24 | b, _ := ioutil.ReadAll(rr.Body) 25 | jwt := string(b) 26 | 27 | req = httptest.NewRequest(http.MethodGet, "/api/v1/books", nil) 28 | req.Header.Add("Authorization", "Bearer "+jwt) 29 | rr = httptest.NewRecorder() 30 | api.router.ServeHTTP(rr, req) 31 | if !(rr.Code == http.StatusOK) { 32 | t.Errorf("код неверен: получили %d, а хотели %d", rr.Code, http.StatusOK) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /13-api/2-api/pkg/api/session.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "encoding/json" 5 | "io" 6 | "net/http" 7 | ) 8 | 9 | type authInfo struct { 10 | Usr string 11 | Pwd string 12 | } 13 | 14 | func (api *API) authSession(w http.ResponseWriter, r *http.Request) { 15 | body, err := io.ReadAll(r.Body) 16 | if err != nil { 17 | http.Error(w, err.Error(), http.StatusInternalServerError) 18 | return 19 | } 20 | var auth authInfo 21 | err = json.Unmarshal(body, &auth) 22 | if err != nil { 23 | http.Error(w, err.Error(), http.StatusInternalServerError) 24 | return 25 | } 26 | 27 | if auth.Usr == "Usr" && auth.Pwd == "Pwd" { 28 | session, _ := api.store.Get(r, "session-cookie") 29 | session.Values["Usr"] = auth.Usr 30 | session.Values["Pwd"] = auth.Pwd 31 | session.Values["authenticated"] = true 32 | err := session.Save(r, w) 33 | if err != nil { 34 | http.Error(w, err.Error(), http.StatusInternalServerError) 35 | return 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /13-api/2-api/pkg/api/session_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "net/http" 7 | "net/http/httptest" 8 | "testing" 9 | ) 10 | 11 | func TestAPI_authSession(t *testing.T) { 12 | data := authInfo{ 13 | Usr: "Usr", 14 | Pwd: "Pwd", 15 | } 16 | payload, _ := json.Marshal(data) 17 | req := httptest.NewRequest(http.MethodPost, "/api/v1/authSession", bytes.NewBuffer(payload)) 18 | rr := httptest.NewRecorder() 19 | api.router.ServeHTTP(rr, req) 20 | if rr.Code != http.StatusOK { 21 | t.Errorf("код неверен: получили %d, а хотели %d", rr.Code, http.StatusOK) 22 | } 23 | t.Log("Response: ", rr.Body) 24 | } 25 | -------------------------------------------------------------------------------- /14-RPC/1-Go-RPC/cmd/client/rpc-client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/rpc" 7 | 8 | "go-core-4/14-rpc/1-go-rpc/pkg/books" 9 | ) 10 | 11 | type book2 struct { 12 | ID int 13 | Title string 14 | Author string 15 | } 16 | 17 | func main() { 18 | // создание клиента RPC 19 | client, err := rpc.Dial("tcp", "localhost:8080") 20 | if err != nil { 21 | log.Fatal("dialing:", err) 22 | } 23 | 24 | // получение списка книг, используя тип данных результата из пакета "books" 25 | var req = &books.Request{} 26 | var data []books.Book 27 | err = client.Call("Server.Books", req, &data) 28 | if err != nil { 29 | log.Fatal(err) 30 | } 31 | fmt.Printf("%+v\n", data) 32 | 33 | // получение книги по ID, используя локальный тип данных 34 | req = &books.Request{ID: 1} 35 | var item book2 36 | client.Call("Server.Book", req, &item) 37 | if err != nil { 38 | log.Fatal(err) 39 | } 40 | fmt.Printf("%+v\n", item) 41 | 42 | // неверное имя функции 43 | err = client.Call("Server.WrongName", req, &item) 44 | if err != nil { 45 | fmt.Println(err) 46 | } 47 | // неверные типы аргументов 48 | err = client.Call("Server.Book", 100, "ABC") 49 | if err != nil { 50 | fmt.Println(err) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /14-RPC/1-Go-RPC/cmd/server/rpc-server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net" 6 | "net/rpc" 7 | 8 | "go-core-4/14-rpc/1-go-rpc/pkg/books" 9 | ) 10 | 11 | // Server - тип данных RCP-сервера. 12 | type Server int 13 | 14 | // Books возвращает список книг. 15 | func (s *Server) Books(req books.Request, resp *[]books.Book) error { 16 | // resp = &allBooks - не работает. 17 | *resp = allBooks 18 | return nil 19 | } 20 | 21 | // Book возвращает книгу по ID 22 | func (s *Server) Book(req books.Request, resp *books.Book) error { 23 | for _, b := range allBooks { 24 | if b.ID == req.ID { 25 | *resp = b 26 | return nil 27 | } 28 | } 29 | return nil 30 | } 31 | 32 | func main() { 33 | srv := new(Server) 34 | err := rpc.Register(srv) 35 | if err != nil { 36 | log.Fatal(err) 37 | } 38 | 39 | // регистрация сетевой службы RPC-сервера 40 | listener, err := net.Listen("tcp4", ":8080") 41 | if err != nil { 42 | log.Fatal(err) 43 | } 44 | 45 | // rpc.Accept(listener) // блокирующая команда. 46 | // цикл обработки клиентских подключений 47 | for { 48 | conn, err := listener.Accept() 49 | if err != nil { 50 | log.Fatal(err) 51 | } 52 | 53 | go rpc.ServeConn(conn) 54 | } 55 | } 56 | 57 | var allBooks = []books.Book{ 58 | { 59 | ID: 1, 60 | Title: "The Lord Of The Rings", 61 | Author: "J.R.R. Tolkien", 62 | }, 63 | { 64 | ID: 2, 65 | Title: "The Chronicles of Amber", 66 | Author: "Roger Zelazny", 67 | }, 68 | } 69 | -------------------------------------------------------------------------------- /14-RPC/1-Go-RPC/pkg/books/books.go: -------------------------------------------------------------------------------- 1 | package books 2 | 3 | type Book struct { 4 | ID int 5 | Title string 6 | Author string 7 | } 8 | 9 | // Request - запрос. 10 | type Request struct { 11 | ID int 12 | Title string 13 | Author string 14 | } 15 | -------------------------------------------------------------------------------- /14-RPC/2-gRPC/books_proto/books.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.28.1 4 | // protoc v4.24.0--rc3 5 | // source: books_proto/books.proto 6 | 7 | package books_proto 8 | 9 | import ( 10 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 | reflect "reflect" 13 | sync "sync" 14 | ) 15 | 16 | const ( 17 | // Verify that this generated code is sufficiently up-to-date. 18 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 19 | // Verify that runtime/protoimpl is sufficiently up-to-date. 20 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 21 | ) 22 | 23 | // Книга. 24 | type Book struct { 25 | state protoimpl.MessageState 26 | sizeCache protoimpl.SizeCache 27 | unknownFields protoimpl.UnknownFields 28 | 29 | Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` 30 | Title string `protobuf:"bytes,2,opt,name=title,proto3" json:"title,omitempty"` 31 | AuthorName string `protobuf:"bytes,3,opt,name=author_name,json=authorName,proto3" json:"author_name,omitempty"` 32 | } 33 | 34 | func (x *Book) Reset() { 35 | *x = Book{} 36 | if protoimpl.UnsafeEnabled { 37 | mi := &file_books_proto_books_proto_msgTypes[0] 38 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 39 | ms.StoreMessageInfo(mi) 40 | } 41 | } 42 | 43 | func (x *Book) String() string { 44 | return protoimpl.X.MessageStringOf(x) 45 | } 46 | 47 | func (*Book) ProtoMessage() {} 48 | 49 | func (x *Book) ProtoReflect() protoreflect.Message { 50 | mi := &file_books_proto_books_proto_msgTypes[0] 51 | if protoimpl.UnsafeEnabled && x != nil { 52 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 53 | if ms.LoadMessageInfo() == nil { 54 | ms.StoreMessageInfo(mi) 55 | } 56 | return ms 57 | } 58 | return mi.MessageOf(x) 59 | } 60 | 61 | // Deprecated: Use Book.ProtoReflect.Descriptor instead. 62 | func (*Book) Descriptor() ([]byte, []int) { 63 | return file_books_proto_books_proto_rawDescGZIP(), []int{0} 64 | } 65 | 66 | func (x *Book) GetId() int64 { 67 | if x != nil { 68 | return x.Id 69 | } 70 | return 0 71 | } 72 | 73 | func (x *Book) GetTitle() string { 74 | if x != nil { 75 | return x.Title 76 | } 77 | return "" 78 | } 79 | 80 | func (x *Book) GetAuthorName() string { 81 | if x != nil { 82 | return x.AuthorName 83 | } 84 | return "" 85 | } 86 | 87 | type Empty struct { 88 | state protoimpl.MessageState 89 | sizeCache protoimpl.SizeCache 90 | unknownFields protoimpl.UnknownFields 91 | } 92 | 93 | func (x *Empty) Reset() { 94 | *x = Empty{} 95 | if protoimpl.UnsafeEnabled { 96 | mi := &file_books_proto_books_proto_msgTypes[1] 97 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 98 | ms.StoreMessageInfo(mi) 99 | } 100 | } 101 | 102 | func (x *Empty) String() string { 103 | return protoimpl.X.MessageStringOf(x) 104 | } 105 | 106 | func (*Empty) ProtoMessage() {} 107 | 108 | func (x *Empty) ProtoReflect() protoreflect.Message { 109 | mi := &file_books_proto_books_proto_msgTypes[1] 110 | if protoimpl.UnsafeEnabled && x != nil { 111 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 112 | if ms.LoadMessageInfo() == nil { 113 | ms.StoreMessageInfo(mi) 114 | } 115 | return ms 116 | } 117 | return mi.MessageOf(x) 118 | } 119 | 120 | // Deprecated: Use Empty.ProtoReflect.Descriptor instead. 121 | func (*Empty) Descriptor() ([]byte, []int) { 122 | return file_books_proto_books_proto_rawDescGZIP(), []int{1} 123 | } 124 | 125 | var File_books_proto_books_proto protoreflect.FileDescriptor 126 | 127 | var file_books_proto_books_proto_rawDesc = []byte{ 128 | 0x0a, 0x17, 0x62, 0x6f, 0x6f, 0x6b, 0x73, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x62, 0x6f, 129 | 0x6f, 0x6b, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 130 | 0x62, 0x75, 0x66, 0x22, 0x4d, 0x0a, 0x04, 0x42, 0x6f, 0x6f, 0x6b, 0x12, 0x0e, 0x0a, 0x02, 0x69, 131 | 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 132 | 0x69, 0x74, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 133 | 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 134 | 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x4e, 0x61, 135 | 0x6d, 0x65, 0x22, 0x07, 0x0a, 0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x32, 0x67, 0x0a, 0x09, 0x42, 136 | 0x6f, 0x6f, 0x6b, 0x69, 0x6e, 0x69, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x05, 0x42, 0x6f, 0x6f, 0x6b, 137 | 0x73, 0x12, 0x0f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 138 | 0x74, 0x79, 0x1a, 0x0e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 139 | 0x6f, 0x6b, 0x22, 0x00, 0x30, 0x01, 0x12, 0x2c, 0x0a, 0x07, 0x41, 0x64, 0x64, 0x42, 0x6f, 0x6f, 140 | 0x6b, 0x12, 0x0e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 141 | 0x6b, 0x1a, 0x0f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 142 | 0x74, 0x79, 0x22, 0x00, 0x42, 0x0f, 0x5a, 0x0d, 0x2e, 0x2f, 0x62, 0x6f, 0x6f, 0x6b, 0x73, 0x5f, 143 | 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 144 | } 145 | 146 | var ( 147 | file_books_proto_books_proto_rawDescOnce sync.Once 148 | file_books_proto_books_proto_rawDescData = file_books_proto_books_proto_rawDesc 149 | ) 150 | 151 | func file_books_proto_books_proto_rawDescGZIP() []byte { 152 | file_books_proto_books_proto_rawDescOnce.Do(func() { 153 | file_books_proto_books_proto_rawDescData = protoimpl.X.CompressGZIP(file_books_proto_books_proto_rawDescData) 154 | }) 155 | return file_books_proto_books_proto_rawDescData 156 | } 157 | 158 | var file_books_proto_books_proto_msgTypes = make([]protoimpl.MessageInfo, 2) 159 | var file_books_proto_books_proto_goTypes = []interface{}{ 160 | (*Book)(nil), // 0: protobuf.Book 161 | (*Empty)(nil), // 1: protobuf.Empty 162 | } 163 | var file_books_proto_books_proto_depIdxs = []int32{ 164 | 1, // 0: protobuf.Bookinist.Books:input_type -> protobuf.Empty 165 | 0, // 1: protobuf.Bookinist.AddBook:input_type -> protobuf.Book 166 | 0, // 2: protobuf.Bookinist.Books:output_type -> protobuf.Book 167 | 1, // 3: protobuf.Bookinist.AddBook:output_type -> protobuf.Empty 168 | 2, // [2:4] is the sub-list for method output_type 169 | 0, // [0:2] is the sub-list for method input_type 170 | 0, // [0:0] is the sub-list for extension type_name 171 | 0, // [0:0] is the sub-list for extension extendee 172 | 0, // [0:0] is the sub-list for field type_name 173 | } 174 | 175 | func init() { file_books_proto_books_proto_init() } 176 | func file_books_proto_books_proto_init() { 177 | if File_books_proto_books_proto != nil { 178 | return 179 | } 180 | if !protoimpl.UnsafeEnabled { 181 | file_books_proto_books_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 182 | switch v := v.(*Book); i { 183 | case 0: 184 | return &v.state 185 | case 1: 186 | return &v.sizeCache 187 | case 2: 188 | return &v.unknownFields 189 | default: 190 | return nil 191 | } 192 | } 193 | file_books_proto_books_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { 194 | switch v := v.(*Empty); i { 195 | case 0: 196 | return &v.state 197 | case 1: 198 | return &v.sizeCache 199 | case 2: 200 | return &v.unknownFields 201 | default: 202 | return nil 203 | } 204 | } 205 | } 206 | type x struct{} 207 | out := protoimpl.TypeBuilder{ 208 | File: protoimpl.DescBuilder{ 209 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 210 | RawDescriptor: file_books_proto_books_proto_rawDesc, 211 | NumEnums: 0, 212 | NumMessages: 2, 213 | NumExtensions: 0, 214 | NumServices: 1, 215 | }, 216 | GoTypes: file_books_proto_books_proto_goTypes, 217 | DependencyIndexes: file_books_proto_books_proto_depIdxs, 218 | MessageInfos: file_books_proto_books_proto_msgTypes, 219 | }.Build() 220 | File_books_proto_books_proto = out.File 221 | file_books_proto_books_proto_rawDesc = nil 222 | file_books_proto_books_proto_goTypes = nil 223 | file_books_proto_books_proto_depIdxs = nil 224 | } 225 | -------------------------------------------------------------------------------- /14-RPC/2-gRPC/books_proto/books.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package protobuf; 3 | 4 | option go_package = "./books_proto"; 5 | 6 | // Книга. 7 | message Book { 8 | int64 id = 1; 9 | string title = 2; 10 | string author_name = 3; 11 | } 12 | 13 | message Empty {} 14 | 15 | // Из каталога 2-gRPC: 16 | // PB: "protoc -I . --go_out=. ./books_proto/books.proto" 17 | // gRPC: "protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative .\books_proto\books.proto" 18 | 19 | // Описание службы gRPC. 20 | service Bookinist { 21 | rpc Books (Empty) returns (stream Book) {} 22 | rpc AddBook (Book) returns (Empty) {} 23 | } -------------------------------------------------------------------------------- /14-RPC/2-gRPC/books_proto/books_grpc.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT. 2 | // versions: 3 | // - protoc-gen-go-grpc v1.2.0 4 | // - protoc v4.24.0--rc3 5 | // source: books_proto/books.proto 6 | 7 | package books_proto 8 | 9 | import ( 10 | context "context" 11 | grpc "google.golang.org/grpc" 12 | codes "google.golang.org/grpc/codes" 13 | status "google.golang.org/grpc/status" 14 | ) 15 | 16 | // This is a compile-time assertion to ensure that this generated file 17 | // is compatible with the grpc package it is being compiled against. 18 | // Requires gRPC-Go v1.32.0 or later. 19 | const _ = grpc.SupportPackageIsVersion7 20 | 21 | // BookinistClient is the client API for Bookinist service. 22 | // 23 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. 24 | type BookinistClient interface { 25 | Books(ctx context.Context, in *Empty, opts ...grpc.CallOption) (Bookinist_BooksClient, error) 26 | AddBook(ctx context.Context, in *Book, opts ...grpc.CallOption) (*Empty, error) 27 | } 28 | 29 | type bookinistClient struct { 30 | cc grpc.ClientConnInterface 31 | } 32 | 33 | func NewBookinistClient(cc grpc.ClientConnInterface) BookinistClient { 34 | return &bookinistClient{cc} 35 | } 36 | 37 | func (c *bookinistClient) Books(ctx context.Context, in *Empty, opts ...grpc.CallOption) (Bookinist_BooksClient, error) { 38 | stream, err := c.cc.NewStream(ctx, &Bookinist_ServiceDesc.Streams[0], "/protobuf.Bookinist/Books", opts...) 39 | if err != nil { 40 | return nil, err 41 | } 42 | x := &bookinistBooksClient{stream} 43 | if err := x.ClientStream.SendMsg(in); err != nil { 44 | return nil, err 45 | } 46 | if err := x.ClientStream.CloseSend(); err != nil { 47 | return nil, err 48 | } 49 | return x, nil 50 | } 51 | 52 | type Bookinist_BooksClient interface { 53 | Recv() (*Book, error) 54 | grpc.ClientStream 55 | } 56 | 57 | type bookinistBooksClient struct { 58 | grpc.ClientStream 59 | } 60 | 61 | func (x *bookinistBooksClient) Recv() (*Book, error) { 62 | m := new(Book) 63 | if err := x.ClientStream.RecvMsg(m); err != nil { 64 | return nil, err 65 | } 66 | return m, nil 67 | } 68 | 69 | func (c *bookinistClient) AddBook(ctx context.Context, in *Book, opts ...grpc.CallOption) (*Empty, error) { 70 | out := new(Empty) 71 | err := c.cc.Invoke(ctx, "/protobuf.Bookinist/AddBook", in, out, opts...) 72 | if err != nil { 73 | return nil, err 74 | } 75 | return out, nil 76 | } 77 | 78 | // BookinistServer is the server API for Bookinist service. 79 | // All implementations must embed UnimplementedBookinistServer 80 | // for forward compatibility 81 | type BookinistServer interface { 82 | Books(*Empty, Bookinist_BooksServer) error 83 | AddBook(context.Context, *Book) (*Empty, error) 84 | mustEmbedUnimplementedBookinistServer() 85 | } 86 | 87 | // UnimplementedBookinistServer must be embedded to have forward compatible implementations. 88 | type UnimplementedBookinistServer struct { 89 | } 90 | 91 | func (UnimplementedBookinistServer) Books(*Empty, Bookinist_BooksServer) error { 92 | return status.Errorf(codes.Unimplemented, "method Books not implemented") 93 | } 94 | func (UnimplementedBookinistServer) AddBook(context.Context, *Book) (*Empty, error) { 95 | return nil, status.Errorf(codes.Unimplemented, "method AddBook not implemented") 96 | } 97 | func (UnimplementedBookinistServer) mustEmbedUnimplementedBookinistServer() {} 98 | 99 | // UnsafeBookinistServer may be embedded to opt out of forward compatibility for this service. 100 | // Use of this interface is not recommended, as added methods to BookinistServer will 101 | // result in compilation errors. 102 | type UnsafeBookinistServer interface { 103 | mustEmbedUnimplementedBookinistServer() 104 | } 105 | 106 | func RegisterBookinistServer(s grpc.ServiceRegistrar, srv BookinistServer) { 107 | s.RegisterService(&Bookinist_ServiceDesc, srv) 108 | } 109 | 110 | func _Bookinist_Books_Handler(srv interface{}, stream grpc.ServerStream) error { 111 | m := new(Empty) 112 | if err := stream.RecvMsg(m); err != nil { 113 | return err 114 | } 115 | return srv.(BookinistServer).Books(m, &bookinistBooksServer{stream}) 116 | } 117 | 118 | type Bookinist_BooksServer interface { 119 | Send(*Book) error 120 | grpc.ServerStream 121 | } 122 | 123 | type bookinistBooksServer struct { 124 | grpc.ServerStream 125 | } 126 | 127 | func (x *bookinistBooksServer) Send(m *Book) error { 128 | return x.ServerStream.SendMsg(m) 129 | } 130 | 131 | func _Bookinist_AddBook_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 132 | in := new(Book) 133 | if err := dec(in); err != nil { 134 | return nil, err 135 | } 136 | if interceptor == nil { 137 | return srv.(BookinistServer).AddBook(ctx, in) 138 | } 139 | info := &grpc.UnaryServerInfo{ 140 | Server: srv, 141 | FullMethod: "/protobuf.Bookinist/AddBook", 142 | } 143 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 144 | return srv.(BookinistServer).AddBook(ctx, req.(*Book)) 145 | } 146 | return interceptor(ctx, in, info, handler) 147 | } 148 | 149 | // Bookinist_ServiceDesc is the grpc.ServiceDesc for Bookinist service. 150 | // It's only intended for direct use with grpc.RegisterService, 151 | // and not to be introspected or modified (even as a copy) 152 | var Bookinist_ServiceDesc = grpc.ServiceDesc{ 153 | ServiceName: "protobuf.Bookinist", 154 | HandlerType: (*BookinistServer)(nil), 155 | Methods: []grpc.MethodDesc{ 156 | { 157 | MethodName: "AddBook", 158 | Handler: _Bookinist_AddBook_Handler, 159 | }, 160 | }, 161 | Streams: []grpc.StreamDesc{ 162 | { 163 | StreamName: "Books", 164 | Handler: _Bookinist_Books_Handler, 165 | ServerStreams: true, 166 | }, 167 | }, 168 | Metadata: "books_proto/books.proto", 169 | } 170 | -------------------------------------------------------------------------------- /14-RPC/2-gRPC/client/grpc-client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "log" 8 | 9 | "google.golang.org/grpc" 10 | "google.golang.org/grpc/credentials/insecure" 11 | 12 | pb "go-core-4/14-rpc/2-grpc/books_proto" 13 | ) 14 | 15 | func main() { 16 | conn, err := grpc.Dial("localhost:12345", 17 | grpc.WithTransportCredentials(insecure.NewCredentials())) 18 | if err != nil { 19 | log.Fatal(err) 20 | } 21 | defer conn.Close() 22 | 23 | client := pb.NewBookinistClient(conn) 24 | 25 | ctx := context.Background() 26 | 27 | err = printAllBooksOnserver(ctx, client) 28 | if err != nil { 29 | log.Fatal(err) 30 | } 31 | 32 | client.AddBook(context.Background(), &pb.Book{Id: 3, Title: "The Lord Of The Rings"}) 33 | 34 | err = printAllBooksOnserver(ctx, client) 35 | if err != nil { 36 | log.Fatal(err) 37 | } 38 | } 39 | 40 | func printAllBooksOnserver(ctx context.Context, client pb.BookinistClient) error { 41 | fmt.Println("\nЗапрашиваю книги на gRPC-сервере.") 42 | stream, err := client.Books(context.Background(), &pb.Empty{}) 43 | if err != nil { 44 | return err 45 | } 46 | 47 | for { 48 | select { 49 | case <-ctx.Done(): 50 | return ctx.Err() 51 | default: 52 | book, err := stream.Recv() 53 | if err == io.EOF { 54 | return nil 55 | } 56 | if err != nil { 57 | return err 58 | } 59 | fmt.Printf("Получена книга: %v\n", book) 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /14-RPC/2-gRPC/server/grpc-server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "net" 7 | 8 | "google.golang.org/grpc" 9 | 10 | pb "go-core-4/14-rpc/2-grpc/books_proto" 11 | ) 12 | 13 | type Bookinist struct { 14 | Data []pb.Book 15 | 16 | // Композиция типов. 17 | pb.UnimplementedBookinistServer 18 | } 19 | 20 | func (b *Bookinist) Books(_ *pb.Empty, stream pb.Bookinist_BooksServer) error { 21 | for i := range b.Data { 22 | stream.Send(&b.Data[i]) 23 | } 24 | return nil 25 | } 26 | 27 | func (b *Bookinist) AddBook(_ context.Context, book *pb.Book) (*pb.Empty, error) { 28 | b.Data = append(b.Data, *book) 29 | return new(pb.Empty), nil 30 | } 31 | 32 | func main() { 33 | srv := Bookinist{} 34 | srv.Data = append(srv.Data, 35 | pb.Book{Id: 2, Title: "The Go Programming Language"}, 36 | pb.Book{Id: 1, Title: "1984"}, 37 | ) 38 | 39 | lis, err := net.Listen("tcp", ":12345") 40 | if err != nil { 41 | log.Fatalf("failed to listen: %v", err) 42 | } 43 | 44 | grpcServer := grpc.NewServer() 45 | pb.RegisterBookinistServer(grpcServer, &srv) 46 | grpcServer.Serve(lis) 47 | } 48 | -------------------------------------------------------------------------------- /15-sql/books_db/1-schema.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Схема учебной БД "Книги". 3 | Имена таблиц во множественном числе. 4 | */ 5 | 6 | /* 7 | Удаляем таблицы, если они существуют. 8 | Удаление производится в обратном относительно создания порядке. 9 | */ 10 | DROP TABLE IF EXISTS books_authors; 11 | DROP TABLE IF EXISTS books; 12 | DROP TABLE IF EXISTS authors; 13 | DROP TABLE IF EXISTS publishers; 14 | 15 | /* 16 | Создаём таблицы БД. 17 | Сначала создаются таблицы, на которые ссылаются вторичные ключи. 18 | */ 19 | -- authors - писатели 20 | CREATE TABLE authors ( 21 | id SERIAL PRIMARY KEY, -- первичный ключ 22 | first_name TEXT NOT NULL DEFAULT '', 23 | last_name TEXT NOT NULL DEFAULT '', 24 | year_of_birth INTEGER NOT NULL DEFAULT 0 25 | ); 26 | 27 | -- publishers - издатели 28 | CREATE TABLE publishers ( 29 | id SERIAL PRIMARY KEY, -- первичный ключ 30 | name TEXT NOT NULL, 31 | website TEXT NOT NULL DEFAULT '' 32 | ); 33 | 34 | -- books - книги 35 | CREATE TABLE books ( 36 | id BIGSERIAL PRIMARY KEY, -- первичный ключ 37 | isbn BIGINT UNIQUE, -- ISBN 38 | title TEXT NOT NULL, -- название 39 | year INTEGER DEFAULT 0, -- год выпуска (максимум текущий + 10) 40 | public_domain BOOLEAN DEFAULT FALSE, -- является ли общественным достоянием 41 | publisher_id INTEGER REFERENCES publishers(id) ON DELETE CASCADE ON UPDATE CASCADE DEFAULT 0, 42 | price INTEGER DEFAULT 0 CHECK (price >= 0), 43 | genres TEXT[] DEFAULT '{"не указано"}', -- жанры 44 | info JSONB DEFAULT '{}' -- сведения: оглавление, описание и пр. 45 | ); 46 | -- индекс на базе бинарного дерева для быстрого поиска по названию книг 47 | CREATE INDEX IF NOT EXISTS books_title_idx ON books USING btree (lower(title)); 48 | 49 | -- связь между книжками и писателями 50 | -- (у одной книги может быть несколько авторов) 51 | CREATE TABLE books_authors ( 52 | id BIGSERIAL PRIMARY KEY, -- первичный ключ 53 | book_id BIGINT NOT NULL REFERENCES books(id), 54 | author_id INTEGER NOT NULL REFERENCES authors(id), 55 | UNIQUE(book_id, author_id) 56 | ); 57 | 58 | -- функция-триггер для проверки года выпуска книги 59 | CREATE OR REPLACE FUNCTION check_book_year() 60 | RETURNS TRIGGER AS $$ 61 | BEGIN 62 | IF NEW.year > (SELECT (extract(year from current_date) - 10)) AND 63 | NEW.year < (SELECT (extract(year from current_date) + 10)) 64 | THEN RETURN NEW; 65 | ELSE RAISE EXCEPTION 'Invalid book year'; --RETURN NULL; 66 | END IF; 67 | END; 68 | $$ LANGUAGE plpgsql; 69 | -- регистрация тригера для таблицы 70 | CREATE OR REPLACE TRIGGER check_book_year BEFORE INSERT OR UPDATE ON books 71 | FOR EACH ROW EXECUTE PROCEDURE check_book_year(); -------------------------------------------------------------------------------- /15-sql/books_db/2-data.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Первичное наполнение БД данными. 3 | Часто требуется для заполнения таблиц, на которые ссылаются вторичные ключи, 4 | имеющие значения по умолчанию. 5 | */ 6 | 7 | INSERT INTO authors (id, first_name) VALUES (0, 'автор не указан'); 8 | INSERT INTO authors (id, first_name) VALUES (1, 'автор неизвестен'); 9 | -- поскольку id установлен принудительно, то необходимо изменить начало послеовательности 10 | ALTER SEQUENCE authors_id_seq RESTART WITH 100; 11 | 12 | INSERT INTO publishers (id, name) VALUES (0, 'издательство не указано'); 13 | ALTER SEQUENCE publishers_id_seq RESTART WITH 100; 14 | 15 | -- insert into books (title, year) VALUES ('Book', 2050); - что будет в результате выполнения? -------------------------------------------------------------------------------- /16-db-apps/1-database-sql/database-sql.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "fmt" 7 | "log" 8 | "os" 9 | 10 | _ "github.com/go-sql-driver/mysql" 11 | ) 12 | 13 | // Использование шаблона "Репозиторий". 14 | type BooksRepo interface { 15 | GetBooks(context.Context) ([]book, error) 16 | AddBook(context.Context, book) (int, error) 17 | } 18 | 19 | // Книжка. 20 | type book struct { 21 | ID int 22 | Title string 23 | Year int 24 | Public bool 25 | PublisherID int 26 | Publisher string 27 | } 28 | 29 | func main() { 30 | // Объект БД - пул подключений к СУБД. 31 | // БД - долгоживущий объект. Следует создавать только один объект для каждой БД. 32 | // Далее этот объект следует передавать как зависимость. 33 | var db *sql.DB 34 | var err error 35 | 36 | // Подключение к БД. 37 | // В зависимости от драйвера, sql.Open может не выполнять фактического подключения, 38 | // а только проверить параметры соединения с БД. 39 | pwd := os.Getenv("mysql_password") 40 | db, err = sql.Open("mysql", "root:"+pwd+"@tcp(ubuntu-server.northeurope.cloudapp.azure.com:3306)/books") 41 | if err != nil { 42 | log.Fatal(err) 43 | } 44 | // Не забываем очищать ресурсы. 45 | defer db.Close() 46 | 47 | // Проверка соединения с БД. На случай, если sql.Open этого не делает. 48 | err = db.Ping() 49 | if err != nil { 50 | log.Fatal(err) 51 | } 52 | 53 | // Получение списка книг. 54 | data, err := books(db) 55 | if err != nil { 56 | log.Fatal(err) 57 | } 58 | fmt.Printf("%+v\n", data) 59 | 60 | // добавление книг одной транзакцией 61 | data = []book{ 62 | { 63 | Title: "The Chronicles of Amber", 64 | Year: 1970, 65 | }, 66 | { 67 | Title: "Dune", 68 | Year: 1965, 69 | }, 70 | } 71 | err = addBooks(db, data) 72 | if err != nil { 73 | log.Fatal(err) 74 | } 75 | 76 | // Получение списка книг. 77 | data, err = books(db) 78 | if err != nil { 79 | log.Fatal(err) 80 | } 81 | fmt.Printf("%+v\n", data) 82 | } 83 | 84 | // books возвращает список книг из БД. 85 | func books(db *sql.DB) ([]book, error) { 86 | // Выполнение запроса выборки данных. 87 | // Query производит следующие действия: 88 | // - подготовка запроса; 89 | // - выполнение запроса; 90 | // - закрытие соединения. 91 | rows, err := db.Query(` 92 | SELECT id, title, year 93 | FROM books 94 | WHERE id >= ? AND id < ?;`, 95 | 0, 10_000, 96 | ) 97 | if err != nil { 98 | return nil, err 99 | } 100 | defer rows.Close() 101 | 102 | var books []book 103 | for rows.Next() { 104 | var b book 105 | err := rows.Scan( 106 | &b.ID, 107 | &b.Title, 108 | &b.Year, 109 | ) 110 | if err != nil { 111 | return nil, err 112 | } 113 | books = append(books, b) 114 | } 115 | err = rows.Err() 116 | if err != nil { 117 | return nil, err 118 | } 119 | 120 | return books, nil 121 | } 122 | 123 | // addBooks добавляет в БД массив книг одной транзакцией. 124 | func addBooks(db *sql.DB, books []book) error { 125 | // начало транзакции 126 | tx, err := db.Begin() 127 | if err != nil { 128 | return err 129 | } 130 | // отмена транзакции в случае ошибки 131 | defer tx.Rollback() 132 | 133 | // подготовка запроса для последующего многократного выполнения 134 | stmt, err := tx.Prepare(`INSERT INTO books(title, year) VALUES (?, ?)`) 135 | if err != nil { 136 | return err 137 | } 138 | defer stmt.Close() 139 | 140 | for _, book := range books { 141 | res, err := stmt.ExecContext(context.TODO(), book.Title, book.Year) 142 | if err != nil { 143 | return err 144 | } 145 | id, _ := res.LastInsertId() 146 | fmt.Println("Создана запись с ID:", id) 147 | } 148 | 149 | // подтверждение транзакции 150 | err = tx.Commit() 151 | if err != nil { 152 | return err 153 | } 154 | 155 | return nil 156 | } 157 | -------------------------------------------------------------------------------- /16-db-apps/2-pgx/pgx.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "os" 8 | 9 | "github.com/jackc/pgx/v5" 10 | "github.com/jackc/pgx/v5/pgxpool" 11 | ) 12 | 13 | // Книжка. 14 | type book struct { 15 | ID int 16 | Title string 17 | Year int 18 | Public bool 19 | PublisherID int 20 | Publisher string 21 | } 22 | 23 | func main() { 24 | ctx := context.Background() 25 | // Подключение к БД. Функция возвращает объект БД. 26 | pwd := os.Getenv("pg_password") 27 | db, err := pgxpool.New(ctx, "postgres://postgres:"+pwd+"@ubuntu-server.northeurope.cloudapp.azure.com/books") 28 | if err != nil { 29 | log.Fatalf("Unable to connect to database: %v\n", err) 30 | } 31 | // Не забываем очищать ресурсы. 32 | defer db.Close() 33 | 34 | // Получение списка книг. 35 | data, err := books(ctx, db) 36 | if err != nil { 37 | log.Fatal(err) 38 | } 39 | fmt.Printf("%+v\n", data) 40 | 41 | // добавление книг одной транзакцией 42 | data = []book{ 43 | { 44 | Title: "The Chronicles of Amber", 45 | Year: 1970, 46 | }, 47 | { 48 | Title: "Dune", 49 | Year: 1965, 50 | }, 51 | } 52 | err = addBooks(ctx, db, data) 53 | if err != nil { 54 | log.Fatal(err) 55 | } 56 | 57 | // Получение списка книг. 58 | data, err = books(ctx, db) 59 | if err != nil { 60 | log.Fatal(err) 61 | } 62 | fmt.Printf("%+v\n", data) 63 | } 64 | 65 | // books возвращает список книг из БД. 66 | func books(ctx context.Context, db *pgxpool.Pool) ([]book, error) { 67 | // Выполнение запроса выборки данных. 68 | // Query производит следующие действия: 69 | // - подготовка запроса; 70 | // - выполнение запроса; 71 | // - закрытие соединения. 72 | rows, err := db.Query(ctx, ` 73 | SELECT id, title, year 74 | FRoM books 75 | WHERE id >= $1 AND id < `, 76 | 0, 77 | ) 78 | if err != nil { 79 | return nil, err 80 | } 81 | defer rows.Close() 82 | 83 | var books []book 84 | for rows.Next() { 85 | var b book 86 | err := rows.Scan( 87 | &b.ID, 88 | &b.Title, 89 | &b.Year, 90 | ) 91 | if err != nil { 92 | return nil, err 93 | } 94 | books = append(books, b) 95 | } 96 | err = rows.Err() 97 | if err != nil { 98 | return nil, err 99 | } 100 | return books, nil 101 | } 102 | 103 | // addBooks добавляет в БД массив книг одной транзакцией. 104 | func addBooks(ctx context.Context, db *pgxpool.Pool, books []book) error { 105 | // начало транзакции 106 | tx, err := db.Begin(ctx) 107 | if err != nil { 108 | return err 109 | } 110 | // отмена транзакции в случае ошибки 111 | defer tx.Rollback(ctx) 112 | 113 | // пакетный запрос 114 | var batch = &pgx.Batch{} 115 | 116 | // добавление заданий в пакет 117 | for _, book := range books { 118 | batch.Queue(`INSERT INTO books(title, year) VALUES ($1, $2)`, book.Title, book.Year) 119 | } 120 | 121 | // отправка пакета в БД (может выполняться для транзакции или соединения) 122 | res := tx.SendBatch(ctx, batch) 123 | 124 | // обязательная операция закрытия соединения 125 | err = res.Close() 126 | if err != nil { 127 | return err 128 | } 129 | 130 | // подтверждение транзакции 131 | return tx.Commit(ctx) 132 | } 133 | -------------------------------------------------------------------------------- /16-db-apps/2-pgx/pgx_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "testing" 7 | 8 | "github.com/jackc/pgx/v5/pgxpool" 9 | ) 10 | 11 | var ( 12 | testDB *pgxpool.Pool 13 | ctx = context.Background() 14 | ) 15 | 16 | func TestMain(m *testing.M) { 17 | var err error 18 | // БД для тетов. 19 | testDB, err = pgxpool.New(context.Background(), "postgres://postgres:GoPassword@ubuntu-server.northeurope.cloudapp.azure.com/books") 20 | if err != nil { 21 | log.Fatalf("Unable to connect to database: %v\n", err) 22 | } 23 | defer testDB.Close() 24 | m.Run() 25 | } 26 | 27 | func Test_books(t *testing.T) { 28 | data, err := books(ctx, testDB) 29 | if err != nil { 30 | t.Fatal(err) 31 | } 32 | t.Logf("%+v\n", data) 33 | } 34 | -------------------------------------------------------------------------------- /16-db-apps/pkg/api/api.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "encoding/json" 5 | "go-core-4/16-db-apps/pkg/db" 6 | "net/http" 7 | ) 8 | 9 | type API struct { 10 | db db.Interface 11 | } 12 | 13 | func New(db db.Interface) *API { 14 | return &API{db: db} 15 | } 16 | 17 | func (api *API) handler(w http.ResponseWriter, r *http.Request) { 18 | books, _ := api.db.Books(r.Context()) 19 | 20 | for _, book := range books { 21 | _ = book 22 | } 23 | 24 | json.NewEncoder(w).Encode(books) 25 | } 26 | -------------------------------------------------------------------------------- /16-db-apps/pkg/db/db.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | type Interface interface { 8 | Books(ctx context.Context) ([]Book, error) 9 | AddBooks(ctx context.Context, books []Book) error 10 | } 11 | 12 | type Book struct { 13 | ID int 14 | Title string 15 | Year int 16 | Public bool 17 | PublisherID int 18 | Publisher string 19 | } 20 | -------------------------------------------------------------------------------- /16-db-apps/pkg/db/memsql/memsql.go: -------------------------------------------------------------------------------- 1 | package memsql 2 | 3 | import ( 4 | "context" 5 | "go-core-4/16-db-apps/pkg/db" 6 | 7 | "github.com/jackc/pgx/v5/pgxpool" 8 | ) 9 | 10 | type DB struct { 11 | pool *pgxpool.Pool 12 | } 13 | 14 | func (db *DB) Books(ctx context.Context) ([]db.Book, error) { 15 | return nil, nil 16 | } 17 | 18 | func (db *DB) AddBooks(ctx context.Context, books []db.Book) error { 19 | return nil 20 | } 21 | -------------------------------------------------------------------------------- /16-db-apps/pkg/db/pgsql/pgsql.go: -------------------------------------------------------------------------------- 1 | package pgsql 2 | 3 | import ( 4 | "context" 5 | "go-core-4/16-db-apps/pkg/db" 6 | 7 | "github.com/jackc/pgx/v5/pgxpool" 8 | ) 9 | 10 | type DB struct { 11 | pool *pgxpool.Pool 12 | } 13 | 14 | func (db *DB) Books(ctx context.Context) ([]db.Book, error) { 15 | return nil, nil 16 | } 17 | 18 | func (db *DB) AddBooks(ctx context.Context, books []db.Book) error { 19 | return nil 20 | } 21 | -------------------------------------------------------------------------------- /16-db-apps/schema.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Схема учебной БД "Книги". Запрос в фомате MySQL. 3 | */ 4 | 5 | /* 6 | Удаляем таблицы, если они существуют. 7 | Удаление производится в обратном относительно создания порядке. 8 | */ 9 | DROP TABLE IF EXISTS books_authors; 10 | DROP TABLE IF EXISTS books; 11 | DROP TABLE IF EXISTS authors; 12 | DROP TABLE IF EXISTS publishers; 13 | 14 | /* 15 | Создаём таблицы БД. 16 | Сначала создаются таблицы, на которые ссылаются вторичные ключи. 17 | */ 18 | -- authors - писатели 19 | CREATE TABLE authors ( 20 | id SERIAL PRIMARY KEY, -- первичный ключ 21 | first_name VARCHAR(20) NOT NULL DEFAULT '', 22 | last_name VARCHAR(20) NOT NULL DEFAULT '', 23 | year_of_birth INTEGER NOT NULL DEFAULT 0 24 | ); 25 | 26 | -- publishers - издатели 27 | CREATE TABLE publishers ( 28 | id SERIAL PRIMARY KEY, -- первичный ключ 29 | name VARCHAR(50) NOT NULL, 30 | website VARCHAR(100) NOT NULL DEFAULT '' 31 | ); 32 | 33 | -- books - книги 34 | CREATE TABLE books ( 35 | id SERIAL PRIMARY KEY, -- первичный ключ 36 | title VARCHAR(50) NOT NULL, -- название 37 | year INTEGER DEFAULT 0, -- год выпуска (максимум текущий + 10) 38 | public_domain BOOLEAN DEFAULT FALSE, -- является ли общественным достоянием 39 | publisher_id INT DEFAULT 0 REFERENCES publishers(id) ON DELETE CASCADE ON UPDATE CASCADE 40 | ); 41 | -- индекс на базе бинарного дерева для быстрого поиска по названию книг 42 | CREATE INDEX books_title_idx ON books(title) USING btree; 43 | 44 | -- связь между книжками и писателями 45 | -- (у одной книги может быть несколько авторов) 46 | CREATE TABLE books_authors ( 47 | id SERIAL PRIMARY KEY, -- первичный ключ 48 | book_id BIGINT NOT NULL REFERENCES books(id), 49 | author_id INTEGER NOT NULL REFERENCES authors(id), 50 | UNIQUE(book_id, author_id) 51 | ); 52 | 53 | /* 54 | Первичное наполнение БД данными. 55 | Часто требуется для заполнения таблиц, на которые ссылаются вторичные ключи, 56 | имеющие значения по умолчанию. 57 | */ 58 | 59 | INSERT INTO authors (id, first_name) VALUES (0, 'автор не указан'); 60 | ALTER TABLE authors AUTO_INCREMENT = 100; 61 | 62 | INSERT INTO publishers (id, name) VALUES (0, 'издательство не указано'); 63 | ALTER TABLE publishers AUTO_INCREMENT = 100; 64 | 65 | INSERT INTO books (title) VALUES ('The Lord Of The Rings'); 66 | INSERT INTO books (title) VALUES ('1984'); 67 | ALTER TABLE books AUTO_INCREMENT = 100; -------------------------------------------------------------------------------- /17-system-design/SOLID/1-SRP/srp.go: -------------------------------------------------------------------------------- 1 | // Пакет srp предоставляет функции для 2 | // получения данных и генерации отчетов. 3 | package srp 4 | 5 | type строка string 6 | type байт byte 7 | type неполадка error 8 | 9 | type КлиентПубличногоОблачногоХранилищаДанных struct { 10 | УчёткаЯндексДиск УчётныеДанные 11 | УчёткаГуглДиск УчётныеДанные 12 | УчёткаАмазонС3 УчётныеДанные 13 | } 14 | 15 | type УчётныеДанные struct { 16 | ИмяПользователя строка 17 | Пароль строка 18 | } 19 | 20 | func НовыйКлиент(учётка УчётныеДанные, типКлиента строка, 21 | ) *КлиентПубличногоОблачногоХранилищаДанных { 22 | var клиент КлиентПубличногоОблачногоХранилищаДанных 23 | switch типКлиента { 24 | case "Яндекс": 25 | // ... 26 | case "Гугол": 27 | // ... 28 | } 29 | return &клиент 30 | } 31 | 32 | func (к *КлиентПубличногоОблачногоХранилищаДанных) ПолучитьФайл( 33 | название строка, 34 | типКлиента строка, 35 | ) ([]байт, неполадка) { 36 | switch типКлиента { 37 | case "Яндекс": 38 | // ... 39 | case "Гугол": 40 | // ... 41 | } 42 | 43 | return nil, nil 44 | } 45 | -------------------------------------------------------------------------------- /17-system-design/SOLID/2-OCP/ocp.go: -------------------------------------------------------------------------------- 1 | package ocp 2 | 3 | import "fmt" 4 | 5 | // Пакет демонстрирует типовое нарушение 6 | // принципа OCP. 7 | // Функции занимаются анализом и обработкой 8 | // результата вместо того, чтобы вернуть 9 | // его вызывающему коду. 10 | // В результате у пользователя пакета появляется 11 | // потребность изменить код пакета, что является 12 | // нарушением принципа OCP. 13 | 14 | func Avg(nums []int) { 15 | if len(nums) == 0 { 16 | return 17 | } 18 | 19 | var num float64 20 | var sum int 21 | for _, n := range nums { 22 | sum += n 23 | } 24 | 25 | num = float64(sum / len(nums)) 26 | 27 | // следует вернуть результат, а не печатать его 28 | fmt.Println(num) 29 | } 30 | 31 | func Max(nums []int) int { 32 | // функция проанализировала входные данные и 33 | // вернула нелогичный результат, хотя для этого 34 | // нет никаких оснований 35 | if len(nums) == 0 { 36 | return -1 37 | } 38 | 39 | var num int 40 | for _, n := range nums { 41 | if n > num { 42 | num = n 43 | } 44 | } 45 | 46 | return num 47 | } 48 | -------------------------------------------------------------------------------- /17-system-design/SOLID/2-OCP/ocp_refactored.go: -------------------------------------------------------------------------------- 1 | package ocp 2 | 3 | /* 4 | func Avg(nums []int) float64 { 5 | if len(nums) == 0 { 6 | return 0 7 | } 8 | var sum int 9 | for _, n := range nums { 10 | sum += n 11 | } 12 | return float64(sum / len(nums)) 13 | } 14 | 15 | func Max(nums []int) (int, bool) { 16 | if len(nums) == 0 { 17 | return 0, false 18 | } 19 | num := nums[0] 20 | for _, n := range nums { 21 | if n > num { 22 | num = n 23 | } 24 | } 25 | return num, true 26 | } 27 | */ 28 | -------------------------------------------------------------------------------- /17-system-design/SOLID/2-OCP/ocp_refactored_test.go: -------------------------------------------------------------------------------- 1 | package ocp 2 | 3 | /* 4 | import "testing" 5 | 6 | func TestAvg(t *testing.T) { 7 | type args struct { 8 | nums []int 9 | } 10 | tests := []struct { 11 | name string 12 | args args 13 | want float64 14 | }{ 15 | { 16 | name: "Test #1", 17 | args: args{nums: []int{1, 2, 3}}, 18 | want: 2.0, 19 | }, 20 | } 21 | for _, tt := range tests { 22 | t.Run(tt.name, func(t *testing.T) { 23 | if got := Avg(tt.args.nums); got != tt.want { 24 | t.Errorf("Avg() = %v, want %v", got, tt.want) 25 | } 26 | }) 27 | } 28 | } 29 | 30 | func TestMax(t *testing.T) { 31 | type args struct { 32 | nums []int 33 | } 34 | tests := []struct { 35 | name string 36 | args args 37 | want int 38 | }{ 39 | { 40 | name: "Test #1", 41 | args: args{nums: []int{1, 2, 3}}, 42 | want: 3, 43 | }, 44 | { 45 | name: "Test #2", 46 | args: args{nums: []int{}}, 47 | want: 0, // логично получить значение по умолчанию 48 | }, 49 | } 50 | for _, tt := range tests { 51 | t.Run(tt.name, func(t *testing.T) { 52 | if got := Max(tt.args.nums); got != tt.want { 53 | t.Errorf("Max() = %v, want %v", got, tt.want) 54 | } 55 | }) 56 | } 57 | } 58 | */ 59 | -------------------------------------------------------------------------------- /17-system-design/SOLID/2-OCP/ocp_test.go: -------------------------------------------------------------------------------- 1 | package ocp 2 | 3 | import "testing" 4 | 5 | func TestAvg(t *testing.T) { 6 | type args struct { 7 | nums []int 8 | } 9 | tests := []struct { 10 | name string 11 | args args 12 | }{ 13 | { 14 | name: "Test #1", 15 | args: args{nums: []int{1, 2, 3}}, 16 | }, 17 | } 18 | for _, tt := range tests { 19 | t.Run(tt.name, func(t *testing.T) { 20 | Avg(tt.args.nums) 21 | // Не с чем сравнить результат! 22 | }) 23 | } 24 | } 25 | 26 | func TestMax(t *testing.T) { 27 | type args struct { 28 | nums []int 29 | } 30 | tests := []struct { 31 | name string 32 | args args 33 | want int 34 | }{ 35 | { 36 | name: "Test #1", 37 | args: args{nums: []int{1, 2, 3}}, 38 | want: 3, 39 | }, 40 | { 41 | name: "Test #2", 42 | args: args{nums: []int{}}, 43 | want: 0, // логично получить значение по умолчанию 44 | }, 45 | } 46 | for _, tt := range tests { 47 | t.Run(tt.name, func(t *testing.T) { 48 | if got := Max(tt.args.nums); got != tt.want { 49 | t.Errorf("Max() = %v, want %v", got, tt.want) 50 | } 51 | }) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /17-system-design/SOLID/3-LSP/lsp.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "log" 8 | "os" 9 | ) 10 | 11 | // Пакет демонстрирует выполнение принципа LSP в Go. 12 | 13 | // Интерфейс с контрактом на представление 14 | // значения некоторого типа данных в виде 15 | // последовательности байт. 16 | type Serializer interface { 17 | Serialize() []byte 18 | } 19 | 20 | type String string 21 | 22 | func (s String) Serialize() []byte { 23 | return []byte(s) 24 | } 25 | 26 | type Bool bool 27 | 28 | func (b Bool) Serialize() []byte { 29 | return []byte(fmt.Sprintf("%v", b)) 30 | } 31 | 32 | // Музыкальный альбом. 33 | type Album struct { 34 | Title String 35 | Year uint 36 | } 37 | 38 | // Реализация контракта. 39 | func (a Album) Serialize() []byte { 40 | b, err := json.Marshal(a) 41 | if err != nil { 42 | return nil 43 | } 44 | return b 45 | } 46 | 47 | // Функция принимает на вход объект, в который требуется 48 | // сохранить состояние: файл, принтер и т.д. 49 | // Также принимается любое количество объектов, поддерживающих 50 | // сериализацию. 51 | func WriteObject(w io.Writer, objects ...Serializer) error { 52 | for _, obj := range objects { 53 | b := obj.Serialize() 54 | _, err := w.Write(b) 55 | if err != nil { 56 | return err 57 | } 58 | } 59 | return nil 60 | } 61 | 62 | func main() { 63 | var s String = "ABC" 64 | var b Bool = true 65 | var a = Album{Title: "Nevermind", Year: 1991} 66 | 67 | f, err := os.Create("./file.txt") 68 | if err != nil { 69 | log.Fatal(err) 70 | } 71 | 72 | err = WriteObject(f, s, b, a) 73 | if err != nil { 74 | log.Fatal(err) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /17-system-design/SOLID/4-ISP/isp.go: -------------------------------------------------------------------------------- 1 | package isp 2 | 3 | // Пакет демонстрирует принцип разделения интерфейсов. 4 | 5 | // Интерфейс, включающий в себя все возможные методы, 6 | // которые используются в пакете. 7 | type BigInterface interface { 8 | Dance() 9 | Sing() 10 | PlayGuitar() 11 | } 12 | 13 | // Минимальный интерфейс, требуемый 14 | // для функции Dance. 15 | type Dancer interface { 16 | Dance() 17 | } 18 | 19 | // Нарушение ISP, поскольку от аргумента требуется 20 | // реализация методов, которые не используются. 21 | func BadDance(dancer BigInterface) { 22 | dancer.Dance() 23 | } 24 | 25 | // Соблюдение ISP за счёт разделения интерфейсов 26 | // для разных задач. 27 | func GoodDance(dancer Dancer) { 28 | dancer.Dance() 29 | } 30 | -------------------------------------------------------------------------------- /17-system-design/SOLID/5-DIP/dip.go: -------------------------------------------------------------------------------- 1 | package dip 2 | 3 | // База данных - деталь реализации. 4 | type DB []Book 5 | 6 | type Book struct { 7 | ID int 8 | Title string 9 | } 10 | 11 | // Интерфейс - часть бизнес-логики. 12 | type Storage interface { 13 | Books() []Book 14 | } 15 | 16 | func (db DB) Books() []Book { 17 | return []Book{} 18 | } 19 | 20 | // Нарушение принципа DIP. 21 | type WrongServer struct { 22 | db DB 23 | } 24 | 25 | // Выполнение принципа DIP. 26 | type GoodServer struct { 27 | db Storage 28 | } 29 | 30 | // Бизнес-логика системы. 31 | func (s *WrongServer) BusinessLogic() { 32 | // Принцип DIP нарушен, обращение к БД. 33 | books := s.db.Books() 34 | _ = books 35 | } 36 | 37 | // Бизнес-логика системы. 38 | func (s *GoodServer) BusinessLogic() { 39 | // Принцип DIP выполняется, обращение к интерфейсу. 40 | books := s.db.Books() 41 | _ = books 42 | } 43 | -------------------------------------------------------------------------------- /17-system-design/app/cmd/appclient/appclient.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func main() {} 4 | -------------------------------------------------------------------------------- /17-system-design/app/cmd/appserver/appserver.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "go-core-4/17-system-design/app/internal/server" 4 | 5 | func main() { 6 | s, _ := server.New() 7 | s.Run() 8 | } 9 | -------------------------------------------------------------------------------- /17-system-design/app/internal/api/api.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "encoding/json" 5 | "go-core-4/17-system-design/app/internal/db" 6 | "go-core-4/17-system-design/app/internal/models" 7 | "net/http" 8 | 9 | "github.com/gorilla/mux" 10 | ) 11 | 12 | // API предоставляет интерфейс программного взаимодействия. 13 | type API struct { 14 | router *mux.Router 15 | db *db.DB 16 | } 17 | 18 | func New(db *db.DB) *API { 19 | api := API{ 20 | router: mux.NewRouter(), 21 | db: db, 22 | } 23 | api.Endpoints() 24 | return &api 25 | } 26 | 27 | func (api *API) Run(addr string) error { 28 | return http.ListenAndServe(addr, api.router) 29 | } 30 | 31 | // Endpoints регистрирует конечные точки API. 32 | func (api *API) Endpoints() { 33 | api.router.HandleFunc("/api/v1/books", api.books).Methods(http.MethodGet) 34 | api.router.HandleFunc("/api/v1/books", api.newBook).Methods(http.MethodPost) 35 | api.router.HandleFunc("/api/v1/books/{id}", api.deleteBook).Methods(http.MethodDelete) 36 | } 37 | 38 | func (api *API) books(w http.ResponseWriter, r *http.Request) { 39 | err := json.NewEncoder(w).Encode(models.Books) 40 | if err != nil { 41 | http.Error(w, err.Error(), http.StatusInternalServerError) 42 | return 43 | } 44 | } 45 | 46 | func (api *API) newBook(w http.ResponseWriter, r *http.Request) { 47 | var b models.Book 48 | err := json.NewDecoder(r.Body).Decode(&b) 49 | if err != nil { 50 | http.Error(w, err.Error(), http.StatusInternalServerError) 51 | return 52 | } 53 | models.Books = append(models.Books, b) 54 | } 55 | 56 | func (api *API) deleteBook(w http.ResponseWriter, r *http.Request) { 57 | } 58 | -------------------------------------------------------------------------------- /17-system-design/app/internal/db/db.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/jackc/pgx/v5/pgxpool" 7 | ) 8 | 9 | type DB struct { 10 | pool *pgxpool.Pool 11 | } 12 | 13 | func New(connString string) (*DB, error) { 14 | pool, err := pgxpool.New(context.Background(), connString) 15 | if err != nil { 16 | return nil, err 17 | } 18 | 19 | return &DB{pool: pool}, nil 20 | } 21 | -------------------------------------------------------------------------------- /17-system-design/app/internal/models/models.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type Book struct { 4 | Name string 5 | Author string 6 | } 7 | 8 | var Books = []Book{ 9 | { 10 | Name: "The Lord Of The Rings", 11 | Author: "J.R.R. Tolkien", 12 | }, 13 | } 14 | -------------------------------------------------------------------------------- /17-system-design/app/internal/server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "go-core-4/17-system-design/app/internal/api" 5 | "go-core-4/17-system-design/app/internal/db" 6 | ) 7 | 8 | type Server struct { 9 | api *api.API 10 | db *db.DB 11 | } 12 | 13 | const connStr = "postgres://..." 14 | 15 | func New() (*Server, error) { 16 | s := Server{} 17 | db, err := db.New(connStr) 18 | if err != nil { 19 | return nil, err 20 | } 21 | s.db = db 22 | s.api = api.New(s.db) 23 | 24 | return &s, nil 25 | } 26 | 27 | func (s *Server) Run() { 28 | // ... 29 | } 30 | -------------------------------------------------------------------------------- /18-microservices/microservice/build/Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | 3 | FROM golang:1.21 4 | 5 | WORKDIR /app 6 | 7 | COPY . . 8 | RUN go mod download 9 | 10 | WORKDIR /app/cmd/app 11 | RUN GOOS=linux go build -o app 12 | 13 | EXPOSE 8080 14 | 15 | CMD ["./app"] 16 | 17 | # docker build -f .\build\Dockerfile --progress=plain -t dmitriytitov/microapp:latest . 18 | # docker run --rm -p 8080:8080 dmitriytitov/microapp:latest -------------------------------------------------------------------------------- /18-microservices/microservice/build/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: microapp-deployment 5 | spec: 6 | selector: 7 | matchLabels: 8 | appname: microapp 9 | replicas: 2 # количество экземпляров приложения 10 | template: 11 | metadata: 12 | labels: 13 | appname: microapp 14 | spec: 15 | containers: 16 | - name: microapp 17 | image: dmitriytitov/microapp:latest 18 | ports: 19 | - containerPort: 8080 20 | 21 | # kubectl port-forward deployment.apps/microapp-deployment 8080:8080 -------------------------------------------------------------------------------- /18-microservices/microservice/build/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: microapp-service 5 | spec: 6 | selector: 7 | appname: microapp 8 | ports: 9 | - protocol: TCP 10 | port: 8080 11 | targetPort: 8080 -------------------------------------------------------------------------------- /18-microservices/microservice/cmd/app/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "microapp/pkg/api" 5 | "net/http" 6 | ) 7 | 8 | func main() { 9 | srv := New() 10 | srv.run() 11 | } 12 | 13 | type Server struct { 14 | api *api.API 15 | } 16 | 17 | func New() *Server { 18 | s := Server{ 19 | api: api.New(), 20 | } 21 | return &s 22 | } 23 | 24 | func (s *Server) run() { 25 | http.ListenAndServe(":8080", s.api.Router) 26 | } 27 | -------------------------------------------------------------------------------- /18-microservices/microservice/go.mod: -------------------------------------------------------------------------------- 1 | module microapp 2 | 3 | go 1.19 4 | 5 | require github.com/gorilla/mux v1.8.0 6 | -------------------------------------------------------------------------------- /18-microservices/microservice/go.sum: -------------------------------------------------------------------------------- 1 | github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= 2 | github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= 3 | -------------------------------------------------------------------------------- /18-microservices/microservice/pkg/api/api.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "os" 7 | "sync" 8 | 9 | "github.com/gorilla/mux" 10 | ) 11 | 12 | type API struct { 13 | Router *mux.Router 14 | 15 | sync.Mutex 16 | db []Review 17 | } 18 | 19 | func New() *API { 20 | api := API{ 21 | Router: mux.NewRouter(), 22 | } 23 | 24 | api.Router.HandleFunc("/", handler) 25 | 26 | return &api 27 | } 28 | 29 | type Review struct { 30 | MocieID int 31 | Text string 32 | } 33 | 34 | func handler(w http.ResponseWriter, r *http.Request) { 35 | hostname, _ := os.Hostname() 36 | w.Write([]byte(fmt.Sprintf("Microapp на машине %v", hostname))) 37 | } 38 | -------------------------------------------------------------------------------- /19-queue/1-kafka/kafka.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "log" 7 | "time" 8 | 9 | "github.com/segmentio/kafka-go" 10 | ) 11 | 12 | // Client - клиент очереди Kafka. 13 | type Client struct { 14 | Reader *kafka.Reader 15 | Writer *kafka.Writer 16 | } 17 | 18 | // New создает и инициализирует клиента Kafka. 19 | func New(brokers []string, topic string, groupId string) (*Client, error) { 20 | if len(brokers) == 0 || brokers[0] == "" || topic == "" || groupId == "" { 21 | return nil, errors.New("не указаны параметры подключения к Kafka") 22 | } 23 | 24 | c := Client{} 25 | 26 | c.Reader = kafka.NewReader(kafka.ReaderConfig{ 27 | Brokers: brokers, 28 | Topic: topic, 29 | GroupID: groupId, 30 | MinBytes: 10e1, 31 | MaxBytes: 10e6, 32 | }) 33 | 34 | c.Writer = &kafka.Writer{ 35 | Addr: kafka.TCP(brokers[0]), 36 | Topic: topic, 37 | Balancer: &kafka.LeastBytes{}, 38 | AllowAutoTopicCreation: true, 39 | } 40 | 41 | return &c, nil 42 | } 43 | 44 | func main() { 45 | // Инициализация клиента Kafka. 46 | kfk, err := New( 47 | []string{"localhost:29092"}, 48 | "test-topic", 49 | "test-consumer-group", 50 | ) 51 | if err != nil { 52 | log.Fatal(err) 53 | } 54 | 55 | go kfk.producer() 56 | kfk.consumer() 57 | } 58 | 59 | func (c *Client) producer() { 60 | for { 61 | msg := kafka.Message{ 62 | Value: []byte(time.Now().String()), 63 | } 64 | err := c.Writer.WriteMessages(context.Background(), msg) 65 | if err != nil { 66 | log.Println(err) 67 | } 68 | time.Sleep(time.Second * 1) 69 | } 70 | } 71 | 72 | func (c *Client) consumer() { 73 | for { 74 | msg, err := c.Reader.FetchMessage(context.Background()) 75 | if err != nil { 76 | log.Println(err) 77 | } 78 | 79 | log.Println(string(msg.Value)) 80 | 81 | err = c.Reader.CommitMessages(context.Background(), msg) 82 | if err != nil { 83 | log.Println(err) 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /19-queue/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | zookeeper: 4 | image: confluentinc/cp-zookeeper:latest 5 | environment: 6 | ZOOKEEPER_CLIENT_PORT: 2181 7 | ZOOKEEPER_TICK_TIME: 2000 8 | ports: 9 | - 22181:2181 10 | 11 | kafka: 12 | image: confluentinc/cp-kafka:latest 13 | depends_on: 14 | - zookeeper 15 | ports: 16 | - 29092:29092 17 | environment: 18 | KAFKA_BROKER_ID: 1 19 | KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 20 | KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,PLAINTEXT_HOST://localhost:29092 21 | KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT 22 | KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT 23 | KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 -------------------------------------------------------------------------------- /20-NoSQL/1-mongo/mongo.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | 8 | "go.mongodb.org/mongo-driver/bson" 9 | "go.mongodb.org/mongo-driver/mongo" 10 | "go.mongodb.org/mongo-driver/mongo/options" 11 | ) 12 | 13 | const ( 14 | databaseName = "data" // имя учебной БД 15 | collectionName = "languages" // имя коллекции в учебной БД 16 | ) 17 | 18 | type lang struct { 19 | ID int 20 | Name string 21 | } 22 | 23 | func main() { 24 | // подключение к СУБД MongoDB 25 | mongoOpts := options.Client().ApplyURI("mongodb://localhost:27017/") 26 | client, err := mongo.Connect(context.Background(), mongoOpts) 27 | if err != nil { 28 | log.Fatal(err) 29 | } 30 | 31 | // не забываем закрывать ресурсы 32 | defer client.Disconnect(context.Background()) 33 | 34 | // проверка связи с БД 35 | err = client.Ping(context.Background(), nil) 36 | if err != nil { 37 | log.Fatal(err) 38 | } 39 | 40 | // вставка пары документов в БД 41 | data := []lang{ 42 | {ID: 1, Name: "Go"}, 43 | {ID: 2, Name: "JavaScript"}, 44 | } 45 | err = insertDocs(client, data) 46 | if err != nil { 47 | log.Fatal(err) 48 | } 49 | 50 | // получение документов из БД 51 | docs, err := docs(client) 52 | if err != nil { 53 | log.Fatal(err) 54 | } 55 | fmt.Println(docs) 56 | } 57 | 58 | // insertDocs вставляет в БД моссив документов. 59 | func insertDocs(c *mongo.Client, data []lang) error { 60 | collection := c.Database(databaseName).Collection(collectionName) 61 | for _, doc := range data { 62 | _, err := collection.InsertOne(context.Background(), doc) 63 | if err != nil { 64 | return err 65 | } 66 | } 67 | return nil 68 | } 69 | 70 | // docs возвращает все документы из БД. 71 | func docs(c *mongo.Client) ([]lang, error) { 72 | collection := c.Database(databaseName).Collection(collectionName) 73 | filter := bson.D{} 74 | 75 | cur, err := collection.Find(context.Background(), filter) 76 | if err != nil { 77 | return nil, err 78 | } 79 | defer cur.Close(context.Background()) 80 | 81 | var data []lang 82 | for cur.Next(context.Background()) { 83 | var l lang 84 | err := cur.Decode(&l) 85 | if err != nil { 86 | return nil, err 87 | } 88 | data = append(data, l) 89 | } 90 | 91 | return data, cur.Err() 92 | } 93 | -------------------------------------------------------------------------------- /20-NoSQL/2-KVStore/cmd/app/app.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "go-core-4/20-NoSQL/2-KVStore/pkg/kvdb" 6 | ) 7 | 8 | func main() { 9 | db := kvdb.New() 10 | db.SET("1", "test record") 11 | fmt.Println(db.GET("1"), db.GET("2")) 12 | } 13 | -------------------------------------------------------------------------------- /20-NoSQL/2-KVStore/pkg/kvdb/kvdb.go: -------------------------------------------------------------------------------- 1 | package kvdb 2 | 3 | // DB - БД "ключ-значение" 4 | type DB map[string]string 5 | 6 | func New() DB { 7 | return make(map[string]string) 8 | } 9 | 10 | func (db DB) GET(key string) string { 11 | if val, ok := db[key]; ok { 12 | return val 13 | } 14 | return "" 15 | } 16 | 17 | func (db DB) SET(key string, val string) { 18 | db[key] = val 19 | } 20 | -------------------------------------------------------------------------------- /20-NoSQL/3-redis/redis.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "log" 8 | "strconv" 9 | "time" 10 | 11 | // импорт драйвера 12 | "github.com/go-redis/redis/v8" 13 | ) 14 | 15 | // сущность для хранения в СУБД 16 | type book struct { 17 | ID int 18 | Title string 19 | } 20 | 21 | func main() { 22 | // подключение к СУБД 23 | redisClient := redis.NewClient(&redis.Options{ 24 | Addr: "localhost:6379", 25 | Password: "", // без пароля 26 | DB: 0, // БД по умолчанию 27 | }) 28 | 29 | books := []book{ 30 | {ID: 1, Title: "1984"}, 31 | {ID: 2, Title: "Clean Architecture"}, 32 | } 33 | 34 | // выполнение запросов 35 | err := setBooks(redisClient, books) 36 | if err != nil { 37 | log.Fatal(err) 38 | } 39 | 40 | data, err := getBooks(redisClient, []int{1, 2}) 41 | if err != nil { 42 | log.Fatal(err) 43 | } 44 | fmt.Println(data) 45 | } 46 | 47 | // getBooks возвращает книги из кэша. 48 | func getBooks(client *redis.Client, ids []int) ([]book, error) { 49 | var books []book 50 | 51 | for _, id := range ids { 52 | cmd := client.Get(context.Background(), "books:"+strconv.Itoa(id)) 53 | var b book 54 | 55 | err := json.Unmarshal([]byte(cmd.Val()), &b) 56 | if err != nil { 57 | return nil, err 58 | } 59 | 60 | books = append(books, b) 61 | } 62 | 63 | return books, nil 64 | } 65 | 66 | // setBooks обновляет данные в кэше Redis. 67 | func setBooks(client *redis.Client, books []book) error { 68 | for _, b := range books { 69 | key := "books:" + strconv.Itoa(b.ID) 70 | 71 | val, err := json.Marshal(b) 72 | if err != nil { 73 | return err 74 | } 75 | 76 | err = client.Set(context.Background(), key, string(val), time.Minute*10).Err() 77 | if err != nil { 78 | return err 79 | } 80 | } 81 | 82 | return nil 83 | } 84 | -------------------------------------------------------------------------------- /20-NoSQL/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | mongo: 3 | image: mongo 4 | volumes: 5 | - /data/db/mongo 6 | ports: 7 | - "27017:27017" 8 | 9 | redis: 10 | image: redis 11 | volumes: 12 | - /data/db/redis 13 | ports: 14 | - "6379:6379" 15 | -------------------------------------------------------------------------------- /21-interview/01-basics.go: -------------------------------------------------------------------------------- 1 | package interview 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func TypicalErrorsNoSync() { 9 | for i := 0; i < 10; i++ { 10 | go func() { 11 | fmt.Println(i) 12 | }() 13 | } 14 | } 15 | 16 | func TypicalErrorsIndex() { 17 | var wg sync.WaitGroup 18 | wg.Add(10) 19 | 20 | for i := 0; i < 10; i++ { 21 | go func() { 22 | fmt.Println(i) 23 | wg.Done() 24 | }() 25 | } 26 | 27 | wg.Wait() 28 | } 29 | 30 | func Slices() { 31 | s1 := []int{1, 2, 3, 4} 32 | s2 := s1[1:3] 33 | fmt.Println("До изменения S2") 34 | fmt.Println(s1) 35 | fmt.Println(s2) 36 | 37 | s2[1] = 999 38 | fmt.Println("После изменения S2") 39 | fmt.Println(s1) 40 | fmt.Println(s2) 41 | 42 | s2 = append(s2, 5, 6, 7, 8, 9) 43 | fmt.Println("После добавления к S2") 44 | fmt.Println(s1) 45 | fmt.Println(s2) 46 | } 47 | 48 | func Strings() { 49 | s := "Привет!" 50 | 51 | for index, rune := range s { 52 | fmt.Println(index, rune) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /21-interview/01-basics_test.go: -------------------------------------------------------------------------------- 1 | package interview 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestTypicalErrorsNoSync(t *testing.T) { 8 | TypicalErrorsNoSync() 9 | } 10 | 11 | func TestTypicalErrorsIndex(t *testing.T) { 12 | TypicalErrorsIndex() 13 | } 14 | 15 | func TestSlices(t *testing.T) { 16 | Slices() 17 | } 18 | 19 | func TestStrings(t *testing.T) { 20 | Strings() 21 | } 22 | -------------------------------------------------------------------------------- /21-interview/02-channels.go: -------------------------------------------------------------------------------- 1 | package interview 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func Select() { 9 | ch := make(chan string) 10 | select { 11 | case ch <- "string": 12 | fmt.Println("получилось записать в канал") 13 | default: 14 | fmt.Println("не получилось записать в канал") 15 | } 16 | } 17 | 18 | func ProduceConsume() { 19 | ch := make(chan int) 20 | 21 | go func() { 22 | for i := 0; i < 10; i++ { 23 | ch <- i 24 | } 25 | }() 26 | 27 | for val := range ch { 28 | fmt.Println(val) 29 | } 30 | } 31 | 32 | func FanIn[T any](channels ...<-chan T) <-chan T { 33 | ch := make(chan T) 34 | var wg sync.WaitGroup 35 | wg.Add(len(channels)) 36 | 37 | for _, c := range channels { 38 | go func(in <-chan T) { 39 | defer wg.Done() 40 | for i := range in { 41 | ch <- i 42 | } 43 | }(c) 44 | } 45 | 46 | go func() { 47 | wg.Wait() 48 | close(ch) 49 | }() 50 | 51 | return ch 52 | } 53 | -------------------------------------------------------------------------------- /21-interview/02-channels_test.go: -------------------------------------------------------------------------------- 1 | package interview 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestSelect(t *testing.T) { 8 | Select() 9 | } 10 | 11 | func TestProduceConsume(t *testing.T) { 12 | ProduceConsume() 13 | } 14 | 15 | func TestFanIn(t *testing.T) { 16 | ch1 := make(chan int) 17 | ch2 := make(chan int) 18 | go func() { 19 | for i := 0; i < 10; i++ { 20 | ch1 <- i 21 | } 22 | }() 23 | go func() { 24 | for i := 11; i < 20; i++ { 25 | ch1 <- i 26 | } 27 | }() 28 | 29 | ch := FanIn(ch1, ch2) 30 | for val := range ch { 31 | t.Log(val) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /GoSearch/cmd/gosearch/gosearh.go: -------------------------------------------------------------------------------- 1 | package main 2 | -------------------------------------------------------------------------------- /GoSearch/pkg/crawler/crawler.go: -------------------------------------------------------------------------------- 1 | package crawler 2 | 3 | // Поисковый робот. 4 | // Осуществляет сканирование сайтов. 5 | 6 | // Interface определяет контракт поискового робота. 7 | type Interface interface { 8 | Scan(url string, depth int) ([]Document, error) 9 | BatchScan(urls []string, depth int, workers int) (<-chan Document, <-chan error) 10 | } 11 | 12 | // Document - документ, веб-страница, полученная поисковым роботом. 13 | type Document struct { 14 | ID int 15 | URL string 16 | Title string 17 | Body string 18 | } 19 | -------------------------------------------------------------------------------- /GoSearch/pkg/crawler/membot/membot.go: -------------------------------------------------------------------------------- 1 | package membot 2 | 3 | import ( 4 | "go-core-4/gosearch/pkg/crawler" 5 | ) 6 | 7 | // Service - имитация служба поискового робота. 8 | type Service struct{} 9 | 10 | // New - констрктор имитации службы поискового робота. 11 | func New() *Service { 12 | s := Service{} 13 | return &s 14 | } 15 | 16 | // Scan возвращает заранее подготовленный набор данных 17 | func (s *Service) Scan(url string, depth int) ([]crawler.Document, error) { 18 | 19 | data := []crawler.Document{ 20 | { 21 | ID: 0, 22 | URL: "https://yandex.ru", 23 | Title: "Яндекс", 24 | }, 25 | { 26 | ID: 1, 27 | URL: "https://google.ru", 28 | Title: "Google", 29 | }, 30 | } 31 | 32 | return data, nil 33 | } 34 | -------------------------------------------------------------------------------- /GoSearch/pkg/crawler/spider/spider.go: -------------------------------------------------------------------------------- 1 | // Package spider реализует сканер содержимого веб-сайтов. 2 | // Пакет позволяет получить список ссылок и заголовков страниц внутри веб-сайта по его URL. 3 | package spider 4 | 5 | import ( 6 | "net/http" 7 | "strings" 8 | 9 | "go-core-4/gosearch/pkg/crawler" 10 | 11 | "golang.org/x/net/html" 12 | ) 13 | 14 | // Service - служба поискового робота. 15 | type Service struct{} 16 | 17 | // New - констрктор службы поискового робота. 18 | func New() *Service { 19 | s := Service{} 20 | return &s 21 | } 22 | 23 | // Scan осуществляет рекурсивный обход ссылок сайта, указанного в URL, 24 | // с учётом глубины перехода по ссылкам, переданной в depth. 25 | func (s *Service) Scan(url string, depth int) (data []crawler.Document, err error) { 26 | pages := make(map[string]string) 27 | 28 | parse(url, url, depth, pages) 29 | 30 | for url, title := range pages { 31 | item := crawler.Document{ 32 | URL: url, 33 | Title: title, 34 | } 35 | data = append(data, item) 36 | } 37 | 38 | return data, nil 39 | } 40 | 41 | // parse рекурсивно обходит ссылки на странице, переданной в url. 42 | // Глубина рекурсии задаётся в depth. 43 | // Каждая найденная ссылка записывается в ассоциативный массив 44 | // data вместе с названием страницы. 45 | func parse(url, baseurl string, depth int, data map[string]string) error { 46 | if depth == 0 { 47 | return nil 48 | } 49 | 50 | response, err := http.Get(url) 51 | if err != nil { 52 | return err 53 | } 54 | page, err := html.Parse(response.Body) 55 | if err != nil { 56 | return err 57 | } 58 | 59 | data[url] = pageTitle(page) 60 | 61 | if depth == 1 { 62 | return nil 63 | } 64 | links := pageLinks(nil, page) 65 | for _, link := range links { 66 | link = strings.TrimSuffix(link, "/") 67 | // относительная ссылка 68 | if strings.HasPrefix(link, "/") && len(link) > 1 { 69 | link = baseurl + link 70 | } 71 | // ссылка уже отсканирована 72 | if data[link] != "" { 73 | continue 74 | } 75 | // ссылка содержит базовый url полностью 76 | if strings.HasPrefix(link, baseurl) { 77 | parse(link, baseurl, depth-1, data) 78 | } 79 | } 80 | 81 | return nil 82 | } 83 | 84 | // pageTitle осуществляет рекурсивный обход HTML-страницы и возвращает значение элемента . 85 | func pageTitle(n *html.Node) string { 86 | var title string 87 | if n.Type == html.ElementNode && n.Data == "title" && n.FirstChild != nil { 88 | return n.FirstChild.Data 89 | } 90 | for c := n.FirstChild; c != nil; c = c.NextSibling { 91 | title = pageTitle(c) 92 | if title != "" { 93 | break 94 | } 95 | } 96 | return title 97 | } 98 | 99 | // pageLinks рекурсивно сканирует узлы HTML-страницы и возвращает все найденные ссылки без дубликатов. 100 | func pageLinks(links []string, n *html.Node) []string { 101 | if n.Type == html.ElementNode && n.Data == "a" { 102 | for _, a := range n.Attr { 103 | if a.Key == "href" { 104 | if !sliceContains(links, a.Val) { 105 | links = append(links, a.Val) 106 | } 107 | } 108 | } 109 | } 110 | for c := n.FirstChild; c != nil; c = c.NextSibling { 111 | links = pageLinks(links, c) 112 | } 113 | return links 114 | } 115 | 116 | // sliceContains возвращает true если массив содержит переданное значение 117 | func sliceContains(slice []string, value string) bool { 118 | for _, v := range slice { 119 | if v == value { 120 | return true 121 | } 122 | } 123 | return false 124 | } 125 | -------------------------------------------------------------------------------- /GoSearch/pkg/crawler/spider/spider_test.go: -------------------------------------------------------------------------------- 1 | package spider 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **go-core-4** 2 | 3 | # Thinknetica 4 | 5 | Примеры кода для четвертого потока курса "Современная разработка на Go". 6 | Автор: Дмитрий Титов. 7 | -------------------------------------------------------------------------------- /file.txt: -------------------------------------------------------------------------------- 1 | ABCtrue{"Title":"Nevermind","Year":1991} -------------------------------------------------------------------------------- /final.md: -------------------------------------------------------------------------------- 1 | # Итоговый проект 2 | ## Сервис хранения коротких гиперссылок (URL Shortener) 3 | 4 | Пример: https://www.shorturl.at/ 5 | 6 | ### Название 7 | Наше приложение будет называться **Lynks**. 8 | 9 | ### Возможности сервиса 10 | Сервис должен предоставлять пользователям следующие взможности: 11 | 1. Принимать на вход длинную ссылку, формировать на её основе короткую на базе собственного домена, сохранять в БД пару ссылок и возвращать пользователю короткую ссылку. 12 | 2. При переходе пользователя по короткой ссылке на домен нашего сервиса, программа должна запросить из БД длинную ссылку на основе короткой и выполнить перенаправление пользователя на оригинальную страницу. 13 | 14 | ### Пример 15 | Допустим, что наш сервис работает на домене **lynks.org**. Тогда, если пользователь выполняет HTTP-запрос: 16 | ``` 17 | curl --location 'https://lynks.org' \ 18 | --header 'Content-Type: application/json' \ 19 | --data '{ 20 | "destination": "https://github.com/thinknetica/go_course_4" 21 | }' 22 | ``` 23 | 24 | То сервис может вернуть примерно такой ответ: 25 | ``` 26 | { 27 | "shortUrl": "https://lynks.org/qz6d7", 28 | "destination": "https://github.com/thinknetica/go_course_4" 29 | } 30 | ``` 31 | Далее, если пользователь перейдёт по ссылке `https://lynks.org/qz6d7` то сервис выполнит HTTP Redirect (https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections) на страницу `https://github.com/thinknetica/go_course_4`. 32 | 33 | ### Состав компонентов и схема 34 | Сервис должен состоять из следующих компонентов: 35 | 1. **Микросервис коротких гиперссылок.** Этот микросервис должен предоставлять методы API для формирования короткой ссылки и выполнять переход по короткой ссылке на оригинальную страницу. 36 | 2. **БД микросервиса коротких ссылок.** Это должна быть СУБД PostgreSQL с одной таблицей для хранения пар гиперссылок. Для таблицы нужно создать индекс для эффективного поиска длинной гиперссылки по короткой. 37 | 3. **Микросервис кэширования.** Этот микросервис должен ускорить поиск гиперссылок за счёт хранения недавно созданных пар ссылок в быстрой in-memory БД. Микросервис кэширования должен предоставлять метод API, который бы принимал и сохранял в БД пару гиперссылок. Также должен быть метод API для получения длинной ссылки на основе короткой. 38 | 4. **БД микросервиса кэширования.** Следует использовать СУБД Redis для хранения и быстрого доступа к парам гиперссылок. 39 | 40 | ### Структура приложения 41 | Каждый микросервис должен иметь структуру каталогов как принято в курсе (см. занятие по архитектуре приложения). 42 | Пример структуры каталогов проекта находится в репозитории курса: https://github.com/thinknetica/go_course_4/tree/master/lynks 43 | Можно разместить оба микросервиса в одном общем каталоге внутри модуля всего курса. Создавать отдельный репозиторий не требуется. 44 | 45 | ### Дополнительные требования 46 | - Микросервисы должны предоставлять HTTP API. 47 | - Для имён запросов нужно следовать REST-методологии (коллекция-ресурс). 48 | - Для получения данных должен использоваться метод GET, для записи - POST. 49 | - Тело запросов и ответов должно быть в формате JSON. 50 | - Базы данных следует развернуть в виде контейнеров. 51 | - Микросервисы также следует запускать в контейнере. Нужно добавить Dockerfile для каждого сервиса. 52 | - Нужно сделать итоговый Docker Compose файл, который сможет запустить всё приложение целиком. 53 | - Каждый микросервис должен подсчитывать метрики запросов в формате Prometheus: количество запросов к каждому методу API (CounterVec) и время обработки запроса (HistogramVec). Пример: https://prometheus.io/docs/guides/go-application/ 54 | - Микросервисы должны использовать структурированное логирование с помощью пакета https://github.com/rs/zerolog. Следует писать в консоль (stdout) каждое обращение к методам API и все возникающие в работе ошибки. 55 | - БД кэширования должна хранить пары гиперссылок не более 24 часов (следует указать время экспирации). 56 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module go-core-4 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/DmitriyVTitov/size v1.5.0 7 | github.com/dgrijalva/jwt-go v3.2.0+incompatible 8 | github.com/go-redis/redis/v8 v8.11.5 9 | github.com/go-sql-driver/mysql v1.7.1 10 | github.com/gorilla/mux v1.8.0 11 | github.com/gorilla/sessions v1.2.1 12 | github.com/jackc/pgx/v4 v4.18.1 13 | github.com/jackc/pgx/v5 v5.4.3 14 | github.com/rs/zerolog v1.29.1 15 | github.com/segmentio/kafka-go v0.4.42 16 | go.mongodb.org/mongo-driver v1.12.1 17 | golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 18 | golang.org/x/net v0.11.0 19 | google.golang.org/grpc v1.57.0 20 | google.golang.org/protobuf v1.31.0 21 | ) 22 | 23 | require ( 24 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 25 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect 26 | github.com/golang/protobuf v1.5.3 // indirect 27 | github.com/golang/snappy v0.0.1 // indirect 28 | github.com/gorilla/securecookie v1.1.1 // indirect 29 | github.com/jackc/chunkreader/v2 v2.0.1 // indirect 30 | github.com/jackc/pgconn v1.14.0 // indirect 31 | github.com/jackc/pgio v1.0.0 // indirect 32 | github.com/jackc/pgpassfile v1.0.0 // indirect 33 | github.com/jackc/pgproto3/v2 v2.3.2 // indirect 34 | github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect 35 | github.com/jackc/pgtype v1.14.0 // indirect 36 | github.com/jackc/puddle/v2 v2.2.1 // indirect 37 | github.com/klauspost/compress v1.15.9 // indirect 38 | github.com/mattn/go-colorable v0.1.12 // indirect 39 | github.com/mattn/go-isatty v0.0.14 // indirect 40 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect 41 | github.com/pierrec/lz4/v4 v4.1.15 // indirect 42 | github.com/xdg-go/pbkdf2 v1.0.0 // indirect 43 | github.com/xdg-go/scram v1.1.2 // indirect 44 | github.com/xdg-go/stringprep v1.0.4 // indirect 45 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect 46 | golang.org/x/crypto v0.10.0 // indirect 47 | golang.org/x/sync v0.1.0 // indirect 48 | golang.org/x/sys v0.9.0 // indirect 49 | golang.org/x/text v0.10.0 // indirect 50 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect 51 | ) 52 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 | github.com/DmitriyVTitov/size v1.5.0 h1:/PzqxYrOyOUX1BXj6J9OuVRVGe+66VL4D9FlUaW515g= 3 | github.com/DmitriyVTitov/size v1.5.0/go.mod h1:le6rNI4CoLQV1b9gzp1+3d7hMAD/uu2QcJ+aYbNgiU0= 4 | github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= 5 | github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= 6 | github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 7 | github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= 8 | github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= 9 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 10 | github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 11 | github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 12 | github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 13 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 14 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 15 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 16 | github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= 17 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 18 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= 19 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= 20 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= 21 | github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= 22 | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= 23 | github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= 24 | github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= 25 | github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= 26 | github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= 27 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 28 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 29 | github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= 30 | github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= 31 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= 32 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 33 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 34 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= 35 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 36 | github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= 37 | github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 38 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 39 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 40 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 41 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 42 | github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= 43 | github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= 44 | github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= 45 | github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= 46 | github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= 47 | github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= 48 | github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= 49 | github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= 50 | github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= 51 | github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= 52 | github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= 53 | github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= 54 | github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= 55 | github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= 56 | github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= 57 | github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= 58 | github.com/jackc/pgconn v1.14.0 h1:vrbA9Ud87g6JdFWkHTJXppVce58qPIdP7N8y0Ml/A7Q= 59 | github.com/jackc/pgconn v1.14.0/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= 60 | github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= 61 | github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= 62 | github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= 63 | github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= 64 | github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= 65 | github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= 66 | github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= 67 | github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= 68 | github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= 69 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= 70 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= 71 | github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= 72 | github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= 73 | github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= 74 | github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= 75 | github.com/jackc/pgproto3/v2 v2.3.2 h1:7eY55bdBeCz1F2fTzSz69QC+pG46jYq9/jtSPiJ5nn0= 76 | github.com/jackc/pgproto3/v2 v2.3.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= 77 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= 78 | github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= 79 | github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= 80 | github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= 81 | github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= 82 | github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= 83 | github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= 84 | github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw= 85 | github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= 86 | github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= 87 | github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= 88 | github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= 89 | github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= 90 | github.com/jackc/pgx/v4 v4.18.1 h1:YP7G1KABtKpB5IHrO9vYwSrCOhs7p3uqhvhhQBptya0= 91 | github.com/jackc/pgx/v4 v4.18.1/go.mod h1:FydWkUyadDmdNH/mHnGob881GawxeEm7TcMCzkb+qQE= 92 | github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY= 93 | github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA= 94 | github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 95 | github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 96 | github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 97 | github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 98 | github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= 99 | github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= 100 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 101 | github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= 102 | github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY= 103 | github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= 104 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 105 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 106 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 107 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 108 | github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= 109 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 110 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 111 | github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 112 | github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 113 | github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= 114 | github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 115 | github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= 116 | github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 117 | github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= 118 | github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= 119 | github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 120 | github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 121 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 122 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= 123 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 124 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0= 125 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= 126 | github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= 127 | github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= 128 | github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= 129 | github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0= 130 | github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= 131 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 132 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 133 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 134 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 135 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 136 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 137 | github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= 138 | github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= 139 | github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= 140 | github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= 141 | github.com/rs/zerolog v1.29.1 h1:cO+d60CHkknCbvzEWxP0S9K6KqyTjrCNUy1LdQLCGPc= 142 | github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU= 143 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= 144 | github.com/segmentio/kafka-go v0.4.42 h1:qffhBZCz4WcWyNuHEclHjIMLs2slp6mZO8px+5W5tfU= 145 | github.com/segmentio/kafka-go v0.4.42/go.mod h1:d0g15xPMqoUookug0OU75DhGZxXwCFxSLeJ4uphwJzg= 146 | github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= 147 | github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= 148 | github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= 149 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= 150 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 151 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 152 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 153 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= 154 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 155 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 156 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 157 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 158 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 159 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 160 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 161 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 162 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 163 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= 164 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 165 | github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= 166 | github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= 167 | github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= 168 | github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= 169 | github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= 170 | github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= 171 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA= 172 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= 173 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 174 | github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= 175 | go.mongodb.org/mongo-driver v1.12.1 h1:nLkghSU8fQNaK7oUmDhQFsnrtcoNy7Z6LVFKsEecqgE= 176 | go.mongodb.org/mongo-driver v1.12.1/go.mod h1:/rGBTebI3XYboVmgz+Wv3Bcbl3aD0QF9zl6kDDw18rQ= 177 | go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 178 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 179 | go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 180 | go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 181 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 182 | go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= 183 | go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= 184 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= 185 | go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 186 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 187 | go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= 188 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 189 | golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= 190 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 191 | golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 192 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 193 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 194 | golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= 195 | golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 196 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 197 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 198 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 199 | golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= 200 | golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM= 201 | golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= 202 | golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw= 203 | golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= 204 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 205 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 206 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 207 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 208 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 209 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 210 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 211 | golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 212 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 213 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 214 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 215 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 216 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 217 | golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU= 218 | golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= 219 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 220 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 221 | golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= 222 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 223 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 224 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 225 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 226 | golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 227 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 228 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 229 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 230 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 231 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 232 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 233 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 234 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 235 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 236 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 237 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 238 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 239 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 240 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 241 | golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= 242 | golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 243 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 244 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 245 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 246 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 247 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 248 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 249 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 250 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 251 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 252 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 253 | golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= 254 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 255 | golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58= 256 | golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 257 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 258 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 259 | golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 260 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 261 | golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 262 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 263 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 264 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 265 | golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 266 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 267 | golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 268 | golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 269 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 270 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 271 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 272 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 273 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 h1:0nDDozoAU19Qb2HwhXadU8OcsiO/09cnTqhUtq2MEOM= 274 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= 275 | google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw= 276 | google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= 277 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 278 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 279 | google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= 280 | google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 281 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 282 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 283 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 284 | gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= 285 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 286 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 287 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 288 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 289 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 290 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 291 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 292 | -------------------------------------------------------------------------------- /lynks/memcache/cmd/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func main() {} 4 | -------------------------------------------------------------------------------- /lynks/memcache/pkg/redis/redis.go: -------------------------------------------------------------------------------- 1 | package redis 2 | -------------------------------------------------------------------------------- /lynks/shortener/cmd/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func main() {} 4 | -------------------------------------------------------------------------------- /lynks/shortener/pkg/urls/urls.go: -------------------------------------------------------------------------------- 1 | package urls 2 | 3 | func Shorten(src string) string { 4 | return "" 5 | } 6 | --------------------------------------------------------------------------------