КулЛиб - Классная библиотека! Скачать книги бесплатно 

Go. Рецепты программирования [Аарон Торрес] (pdf) читать онлайн

Книга в формате pdf! Изображения и текст могут не отображаться!


 [Настройки текста]  [Cбросить фильтры]
Tlgm: @it_boooks

Tlgm: @it_boooks

Go. Рецепты программирования
Второе издание
Более 85 рецептов для создания модульных, удобочитаемых и
тестируемых приложений Golang в различных областях.

Аарон Торрес

БИРМИНГЕМ — МУМБАИ

Tlgm: @it_boooks

Go. Рецепты
программирования
Вторая редакция
Copyright © 2019 Packt Publishing
Первая редакция: Июнь 2017
Второе издание: Июль 2019
ISBN 978-1-78980-098-2
www.packtpub.com

Tlgm: @it_boooks

Моей жене Кейли и моим дочерям Хейзел, Олеандр и Арании. Спасибо
за ваше терпение, любовь и поддержку. Эта книга была бы
невозможна без вас.

Tlgm: @it_boooks

Contributors
Об авторе
Аарон Торрес получил степень магистра компьютерных наук в Горнотехнологическом институте Нью-Мексико. Он работал над
распределенными системами в области высокопроизводительных
вычислений, а также над крупномасштабными веб-приложениями и
микросервисами. В настоящее время он возглавляет команду
разработчиков Go, которая совершенствует и фокусируется на лучших
практиках Go с упором на непрерывную доставку и автоматическое
тестирование.
Аарон опубликовал ряд статей и имеет несколько патентов в области
хранения и ввода/вывода. Он страстно любит делиться своими
знаниями и идеями с другими. Он также является большим
поклонником языка Go и программного обеспечения с открытым
исходным кодом для серверных систем и разработки.

О рецензенте
Эдуард Бондаренко — разработчик программного обеспечения,
проживающий в Киеве, Украина. Он давно начал программировать на
BASIC на ZXSpectrum. Позже он работал в сфере веб-разработки. Он
использует Ruby on Rails более 8 лет. Долгое время используя Ruby, он
открыл для себя Clojure в начале 2009 года, и ему понравилась
простота языка. Помимо Ruby и Clojure, он интересуется разработкой
Go и ReasonML.
Я хочу поблагодарить мою замечательную жену, детей и родителей
за всю любовь, поддержку и помощь, которые они мне оказывают.

Tlgm: @it_boooks

Table of Contents
Оглавление
Contributors
Об авторе
О рецензенте
Предисловие
Для кого эта книга
Что охватывает эта книга
Чтобы получить максимальную отдачу от этой книги
Загрузите файлы примеров кода
Код в действии
Используемые соглашения
Разделы
Подготовка
Как это сделать…
Как это работает…
Как связаться
Отзывы
1. Ввод-вывод и файловые системы
Технические требования
Использование общих интерфейсов ввода/вывода
Как это сделать…
Как это работает…
Использование пакетов bytes и strings
Как это сделать…
Как это работает…
Работа с каталогами и файлами
Как это сделать…
Как это работает…

Tlgm: @it_boooks

Работа с форматом CSV
Как это сделать…
Как это работает…
Работа с временными файлами
Как это сделать…
Как это работает…
Работа с text/template и html/template
Как это сделать…
Как это работает…
2. Инструменты командной строки
Технические требования
Использование флагов командной строки
Как это сделать…
Как это работает…
Использование аргументов командной строки
Как это сделать…
Как это работает…
Чтение и установка переменных среды
Как это сделать…
Как это работает…
Конфигурация с использованием TOML, YAML и JSON
Как это сделать…
Как это работает…
Работа с каналами Unix
Как это сделать…
Как это работает…
Перехват и обработка сигналов
Как это сделать…
Как это работает…
Приложение для раскрашивания ANSI
Как это сделать…
Как это работает…
3. Преобразование данных и композиция

Tlgm: @it_boooks

Технические требования
Преобразование типов данных и приведение интерфейсов
Как это сделать…
Как это работает…
Работа с числовыми типами данных с использованием math и
math/big
Как это сделать…
Как это работает…
Преобразование валюты и рассмотрение float64
Как это сделать…
Как это работает…
Использование указателей и SQL NullTypes для кодирования
и декодирования
Как это сделать…
Как это работает…
Кодирование и декодирование данных Go
Как это сделать…
Как это работает…
Структурные теги и базовая рефлексия в Go
Как это сделать…
Как это работает…
Реализация коллекций через замыкания
Как это сделать…
Как это работает…
4. Обработка ошибок в Go
Технические требования
Обработка ошибок и интерфейс ошибок
Как это сделать…
Как это работает…
Использование пакета pkg/errors и перенос ошибок
Как это сделать…
Как это работает…

Tlgm: @it_boooks

Использование пакета журнала и понимание того, когда следу
ет регистрировать ошибки
Как это сделать…
Как это работает…
Структурированное ведение журналов с помощью пакетов ap
ex и logrus
Как это сделать…
Как это работает…
Ведение журнала с пакетом context
Как это сделать…
Как это работает…
Использование глобальных переменных уровня пакета
Как это сделать…
Как это работает…
Отлов паники для долго выполняющихся процессов
Как это сделать…
Как это работает…
5. Сетевое программирование
Технические требования
Написание эхо-сервера и клиента TCP/IP
Как это сделать…
Как это работает…
Написание UDP-сервера и клиента
Как это сделать…
Как это работает…
Работа с разрешением доменного имени
Как это сделать…
Как это работает…
Работа с веб-сокетами
Как это сделать…
Как это работает…
Работа с net/rpc для вызова удаленных методов
Как это сделать…

Tlgm: @it_boooks

Как это работает…
Использование net/mail для разбора писем
Как это сделать…
Как это работает…
6. Все о базах данных и хранилищах
Использование пакета database/sql с MySQL
Подготовка
Как это сделать…
Как это работает…
Выполнение интерфейса транзакции базы данных
Подготовка
Как это сделать…
Как это работает…
Пул подключений, ограничение скорости и таймауты для SQ
L
Подготовка
Как это сделать…
Как это работает…
Работа с Redis
Подготовка
Как это сделать…
Как это работает…
Использование NoSQL с MongoDB
Подготовка
Как это сделать…
Как это работает…
Создание интерфейсов хранения для переносимости данных
Подготовка
Как это сделать…
Как это работает…
7. Веб-клиенты и API
Технические требования
Инициализация, хранение и передача структур http.Client

Tlgm: @it_boooks

Как это сделать…
Как это работает…
Написание клиента для REST API
Как это сделать…
Как это работает…
Выполнение параллельных и асинхронных клиентских запро
сов
Как это сделать…
Как это работает…
Использование клиентов OAuth2
Подготовка
Как это сделать…
Как это работает…
Реализация интерфейса хранения токенов OAuth2
Подготовка
Как это сделать…
Как это работает…
Обертывание клиента дополнительным функционалом и фун
кциональной композицией
Как это сделать…
Как это работает…
Понимание клиентов GRPC
Подготовка
Как это сделать…
Как это работает…
Использование twitchtv/twirp для RPC
Подготовка
Как это сделать…
Как это работает…
8. Микросервисы для приложений в Go
Технические требования
Работа с веб-обработчиками, запросами и экземплярами Resp
onseWriter

Tlgm: @it_boooks

Как это сделать…
Как это работает…
Использование структур и замыканий для обработчиков сост
ояния
Как это сделать…
Как это работает…
Проверка входных данных для структур Go и пользовательск
их входных данных
Как это сделать…
Как это работает…
Рендеринг и согласование контента
Как это сделать…
Как это работает…
Внедрение и использование промежуточного ПО
Как это сделать…
Как это работает…
Создание обратного прокси-приложения
Как это сделать…
Как это работает…
Экспорт GRPC как JSON API
Подготовка
Как это сделать…
Как это работает…
9. Тестирование Go-кода
Технические требования
Мокинг с использованием стандартной библиотеки
Как это сделать…
Как это работает…
Использование пакета Mockgen для имитации интерфейсов
Подготовка
Как это сделать…
Как это работает…
Использование табличных тестов для улучшения покрытия

Tlgm: @it_boooks

Как это сделать…
Как это работает…
Использование сторонних инструментов тестирования
Подготовка
Как это сделать…
Как это работает…
Поведенческое тестирование с использованием Go
Подготовка
Как это сделать…
Как это работает…
10. Параллелизм и крнкурентность
Технические требования
Использование каналов и оператора select
Как это сделать…
Как это работает…
Выполнение асинхронных операций с sync.WaitGroup
Как это сделать…
Как это работает…
Использование атомарных операций и мьютекса
Как это сделать…
Как это работает…
Использование пакета context
Как это сделать…
Как это работает…
Выполнение управления состоянием для каналов
Как это сделать…
Как это работает…
Использование шаблона проектирования пула рабочих проце
ссов
Как это сделать…
Как это работает…
Использование воркеров для создания пайплайнов
Как это сделать…

Tlgm: @it_boooks

Как это работает…
11. Распределенные системы
Технические требования
Использование службы обнаружения с Consul
Как это сделать…
Как это работает…
Реализация базового консенсуса с использованием Raft
Как это сделать…
Как это работает…
Использование контейнеризации с Docker
Подготовка
Как это сделать…
Как это работает…
Стратегии оркестрации и развертывания
Как это сделать…
Как это работает…
Приложения для мониторинга
Как это сделать…
Как это работает…
Сбор метрик
Подготовка
Как это сделать…
Как это работает…
12. Реактивное программирование и потоки данных
Технические требования
Использование Goflow для программирования потока данных
Как это сделать…
Как это работает…
Использование Kafka с Sarama
Подготовка
Как это сделать…
Как это работает…
Использование асинхронных продюсеров с Kafka

Tlgm: @it_boooks

Подготовка
Как это сделать…
Как это работает…
Подключение Kafka к Goflow
Подготовка
Как это сделать…
Как это работает…
Пишем сервер GraphQL на Go
Как это сделать…
Как это работает…
13. Бессерверное программирование
Go программирование на Lambda с Apex
Подготовка
Как это сделать…
Как это работает…
Бессерверное ведение журналов и метрик Apex
Подготовка
Как это сделать…
Как это работает…
Google App Engine с Go
Подготовка
Как это сделать…
Как это работает…
Работа с Firebase с помощью firebase.google.com/go
Подготовка
Как это сделать…
Как это работает…
14. Улучшения производительности, советы и рекомендации
Технические требования
Использование инструмента pprof
Как это сделать…
Как это работает…
Бенчмаркинг и поиск узких мест

Tlgm: @it_boooks

Как это сделать…
Как это работает…
Распределение памяти и управление кучей
Как это сделать…
Как это работает…
Использование fasthttprouter и fasthttp
Как это сделать…
Как это работает…

Tlgm: @it_boooks

Предисловие
Спасибо, что выбрали эту книгу! Я надеюсь, что это будет удобным
справочником для разработчиков, чтобы быстро найти шаблоны
разработки Go. Он предназначен для использования в качестве
дополнения к другим ресурсам и справочника, который, как мы
надеемся, будет полезен спустя долгое время после его прочтения.
Каждый рецепт в этой книге включает работающий, простой и
проверенный код, который можно использовать в качестве справочного
материала или основы для ваших собственных приложений. Книга
охватывает широкий спектр тем, от базовых до продвинутых.

Для кого эта книга
Эта книга предназначена для веб-разработчиков, программистов и
корпоративных разработчиков. Предполагается базовое знание языка
Go. Опыт разработки серверных приложений не обязателен, но может
помочь понять мотивацию некоторых рецептов.
Эта книга служит хорошим справочником для разработчиков Go,
которые уже имеют опыт, но нуждаются в быстром напоминании,
примере или справке. Благодаря репозиторию с открытым исходным
кодом должна быть возможность быстро поделиться этими примерами
с командой. Если вы ищете быстрые решения распространенных и не
очень распространенных проблем в программировании на Go, эта
книга для вас.

Что охватывает эта книга
Глава 1,
Ввод-вывод
и
файловые
системы, охватывает
распространенные интерфейсы ввода-вывода Go и исследует работу с
файловыми системами. Сюда входят временные файлы, шаблоны и
файлы CSV.
Глава 2, Инструменты командной строки, рассматривают ввод
данных пользователем через командную строку и исследуют

Tlgm: @it_boooks

обработку распространенных типов данных, таких как TOML, YAML и
JSON.
Глава 3, Преобразование и композиция данных, демонстрирует методы
приведения и преобразования между интерфейсами Go и типами
данных. Она также демонстрирует стратегии кодирования и некоторые
шаблоны функционального проектирования для Go.
Глава 4, Обработка ошибок в Go, демонстрирует стратегии обработки
ошибок в Go. Она исследует, как передавать ошибки, обрабатывать их
и регистрировать.
Глава 5, Сетевое программирование, демонстрирует использование
различных сетевых примитивов, таких как UDP и TCP/IP. Она также
исследует систему доменных имен (DNS), работу с необработанными
сообщениями электронной почты и основы вызова удаленных
процедур (RPC).
Глава 6, Все о базах данных и хранилище, посвящено различным
библиотекам хранения для доступа к системам хранения данных,
таким как MySQL. Она также демонстрирует использование
интерфейсов для отделения вашей библиотеки от логики вашего
приложения.
Глава 7, Веб-клиенты и API, реализует клиентские интерфейсы Go
HTTP, клиенты REST, клиенты OAuth2, украшающие и расширяющие
клиенты, а также gRPC.
Глава 8, Микросервисы для приложений в Go, исследуют вебобработчики,
передачу
состояния
обработчику,
проверку
пользовательского ввода и промежуточное ПО.
Глава 9, Тестирование кода Go, фокусируется на насмешках, тестовом
покрытии, фаззинге, поведенческом тестировании и полезных
инструментах тестирования.
Глава 10, Параллелизм и крнкурентность, предоставляет справочник
по каналам и асинхронным операциям, атомарным значениям,
объектам контекста Go и управлению состоянием канала.
Глава 11, Распределенные системы, реализуют обнаружение сервисов,
контейнеризацию Docker, метрики и мониторинг, а также
оркестрацию. В основном это касается развертывания и производства
приложений Go.

Tlgm: @it_boooks

Глава 12, Реактивное программирование и потоки данных, исследует
реактивные приложения и приложения потока данных, Kafka и
распределенные очереди сообщений, а также серверы GraphQL.
Глава 13, Бессерверное программирование, занимается развертыванием
приложений Go без обслуживания сервера. Сюда входит
использование Google App Engine, Firebase, Lambda и ведение журнала
в бессерверной среде.
Глава 14, Улучшения производительности, советы и рекомендации,
посвящена сравнительному тестированию, выявлению узких мест,
оптимизации и повышению производительности HTTP для
приложений Go.

Чтобы получить максимальную
отдачу от этой книги
Для использования этой книги вам потребуется следующее:
Среда программирования Unix.
Последняя версия серии Go 1.x.
Интернет-соединение.
Разрешение на установку дополнительных пакетов, как описано в
каждой главе.
Предварительные условия и другие требования к установке для
каждого рецепта указаны в разделе «Технические требования»
соответствующих глав.

Загрузите файлы примеров кода
Вы можете загрузить примеры файлов кода для этой книги из своей
учетной записи на сайте www.packtpub.com. Если вы приобрели эту
книгу в другом месте, вы можете посетить сайт www.packtpub.com/sup
port и зарегистрироваться, чтобы файлы были отправлены вам по
электронной почте.
Вы можете скачать файлы кода, выполнив следующие действия:
Войдите или зарегистрируйтесь на www.packtpub.com.
Выберите вкладку SUPPORT.
Нажмите Code Downloads & Errata.

Tlgm: @it_boooks

Введите название книги в поле Search и следуйте инструкциям на
экране.
После загрузки файла убедитесь, что вы распаковали или извлекли
папку, используя последнюю версию:
WinRAR/7-Zip для Windows
Zipeg/iZip/UnRarX для Mac
7-Zip/PeaZip для Linux
Пакет кода для книги также размещен на GitHub по адресу https://githu
b.com/PacktPublishing/Go-Programming-Cookbook-Second-Edition. У нас
также есть другие пакеты кода из нашего богатого каталога книг и
видео, доступных по адресу https://github.com/PacktPublishing/.
Проверь их!

Код в действии
Перейдите по следующей ссылке, чтобы просмотреть видеоролики о
выполнении кода: http://bit.ly/2J2uqQ3

Используемые соглашения
В этой книге используется ряд текстовых соглашений.
CodeInText: указывает кодовые слова в тексте, имена таблиц базы
данных, имена папок, имена файлов, расширения файлов, пути,
фиктивные URL-адреса, пользовательский ввод и дескрипторы Twitter.
Вот пример: «Библиотека bytes предоставляет ряд удобных функций
при работе с данными».
Блок кода устанавливается следующим образом:
b, err := ioutil.ReadAll(r)
if err != nil {
return "", err
}
return string(b), nil
}
Когда мы хотим привлечь ваше внимание к определенной части блока
кода, соответствующие строки или элементы выделяются жирным
шрифтом:

Tlgm: @it_boooks

package bytestrings
import (
"bytes"
"io"
"io/ioutil"
)
Любой ввод или вывод командной строки записывается следующим
образом:
$ go mod init github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/Chapter01/interfaces
Жирный: Обозначает новый термин, важное слово или слова, которые
вы видите на экране. Например, слова в меню или диалоговых окнах
отображаются в тексте следующим образом. Вот пример: «Выберите
Информация о системе в панели администратора».
Предупреждения или важные примечания выглядят
следующим образом.
Советы и рекомендации выглядят следующим образом.

Разделы
В этой книге вы найдете несколько часто встречающихся заголовков
(Подготовка, Как это сделать… и Как это работает…).
Чтобы дать четкие инструкции по завершению рецепта, используйте
эти разделы следующим образом:

Подготовка
В этом разделе рассказывается, чего ожидать от рецепта, и
описывается, как установить любое программное обеспечение или
какие-либо предварительные настройки, необходимые для рецепта.

Tlgm: @it_boooks

Как это сделать…
Этот раздел содержит шаги, необходимые для выполнения рецепта.

Как это работает…
Этот раздел обычно состоит из подробного объяснения того, что
произошло в предыдущем разделе.

Как связаться
Отзывы наших читателей всегда приветствуются.
Общий отзыв: напишите на почту feedback@packtpub.com и укажите
название книги в теме сообщения. Если у вас есть вопросы по какомулибо аспекту этой книги, пожалуйста, напишите нам по адресу questio
ns@packtpub.com.
Исправления: хотя мы приложили все усилия, чтобы обеспечить
точность нашего контента, ошибки случаются. Если вы нашли ошибку
в этой книге, мы будем признательны, если вы сообщите нам об этом.
Посетите веб-сайт www.packtpub.com/submit-errata, выберите свою
книгу, нажмите на ссылку Errata Submission Form и введите данные.
Если вы заинтересованы в том, чтобы стать автором: Если есть
тема, в которой у вас есть опыт, и вы хотите написать или внести свой
вклад в книгу, посетите authors.packtpub.com.

Отзывы
Пожалуйста, оставьте отзыв. После того как вы прочитали и
воспользовались этой книгой, почему бы не оставить отзыв на сайте,
где вы ее приобрели? После этого потенциальные читатели смогут
увидеть ваше непредвзятое мнение и использовать его для принятия
решения о покупке, мы в Packt можем понять, что вы думаете о наших
продуктах, а наши авторы смогут увидеть ваши отзывы об их книге.
Спасибо!
Для получения дополнительной информации о Packt посетите сайт pac
ktpub.com.

Tlgm: @it_boooks

1. Ввод-вывод и файловые
системы
Go обеспечивает отличную поддержку как базового, так и сложного вводавывода. Рецепты в этой главе исследуют общие интерфейсы Go, которые
используются для работы с вводом-выводом, и показывают, как их
использовать. Стандартная библиотека Go часто использует эти интерфейсы,
и они будут использоваться в рецептах на протяжении всей книги.
Вы научитесь работать с данными в памяти и в виде потоков. Вы увидите
примеры работы с файлами, каталогами и форматом CSV. В рецепте
временных файлов рассматривается механизм работы с файлами без
накладных расходов, связанных с конфликтами имен и т. д. Наконец, мы
рассмотрим стандартные шаблоны Go как для простого текста, так и для
HTML.
Эти рецепты должны заложить основу для использования интерфейсов для
представления и изменения данных и должны помочь вам мыслить о данных
абстрактно и гибко.
В этой главе будут рассмотрены следующие рецепты:
Использование общих интерфейсов ввода/вывода
Использование пакетов bytes и strings
Работа с каталогами и файлами
Работа с форматом CSV
Работа с временными файлами
Работа с text/template и html/template

Технические требования
Чтобы продолжить выполнение всех рецептов в этой главе, настройте свою
среду в соответствии со следующими шагами:
Загрузите и установите Go 1.12.6 или более позднюю версию в своей
операционной системе по адресу https://golang.org/doc/install.
Откройте терминал или консольное приложение, создайте и перейдите
в каталог проекта, например ~/projects/go-programming-cookbook.
Весь код будет запускаться и изменяться из этого каталога.
Скопируйте последний код в ~/projects/go-programming-cookbookoriginal, как показано в следующем коде. Рекомендуется работать с

Tlgm: @it_boooks

этим каталогом, а не вводить примеры вручную:
$ git clone git@github.com:PacktPublishing/Go-ProgrammingCookbook-Second-Edition.git go-programming-cookbook-original

Использование общих интерфейсов
ввода/вывода
Язык Go предоставляет ряд интерфейсов ввода-вывода, которые
используются во всей стандартной библиотеке. Лучше всего использовать
эти интерфейсы везде, где это возможно, а не передавать структуры или
другие типы напрямую. Два мощных интерфейса, которые мы рассмотрим в
этом рецепте, — это интерфейсы io.Reader и io.Writer. Эти интерфейсы
используются во всей стандартной библиотеке, и понимание того, как их
использовать, сделает вас лучшим разработчиком Go.
Интерфейсы Reader и Writer выглядят следующим образом:
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
Go также позволяет легко комбинировать интерфейсы. Например, взгляните
на следующий код:
type Seeker interface {
Seek(offset int64, whence int) (int64, error)
}
type ReadSeeker interface {
Reader
Seeker
}
В этом рецепте также исследуется функция io под названием Pipe(), как
показано в следующем коде:
func Pipe() (*PipeReader, *PipeWriter)
Оставшаяся часть этой книги будет использовать эти интерфейсы.

Tlgm: @it_boooks

Как это сделать…
Следующие шаги описывают, как написать и запустить ваше приложение:
В терминале или консольном приложении создайте новый каталог с
именем ~/projects/go-programming-cookbook/chapter1/interfaces.
Перейдите в этот каталог.
Выполните следующую команду:
$ go mod init github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter1/interfaces
Вы должны увидеть файл с именем go.mod, который содержит
следующее:
module github.com/PacktPublishing/Go-Programming-CookbookSecond-Edition/chapter1/interfaces
Скопируйте тесты из ~/projects/go-programming-cookbookoriginal/chapter1/interfaces или используйте это как упражнение
для написания собственного кода!
Создайте файл interfaces.go со следующим содержимым:
package interfaces
import (
"fmt"
"io"
"os"
)
// Copy copies data from in to out first directly,
// then using a buffer. It also writes to stdout
func Copy(in io.ReadSeeker, out io.Writer) error {
// we write to out, but also Stdout
w := io.MultiWriter(out, os.Stdout)
// a standard copy, this can be dangerous if
there's a

// lot of data in in
if _, err := io.Copy(w, in); err != nil {
return err
}
in.Seek(0, 0)

Tlgm: @it_boooks

nil {

// buffered write using 64 byte chunks
buf := make([]byte, 64)
if _, err := io.CopyBuffer(w, in, buf); err !=
return err
}
// lets print a new line
fmt.Println()
return nil

}
Создайте файл с именем pipe.go со следующим содержимым:
package interfaces
import (
"io"
"os"
)
io

// PipeExample helps give some more examples of using
//interfaces
func PipeExample() error {
// the pipe reader and pipe writer implement
// io.Reader and io.Writer
r, w := io.Pipe()
// this needs to be run in a separate go

routine

{

// as it will block waiting for the reader
// close at the end for cleanup
go func() {
// for now we'll write something basic,
// this could also be used to encode json
// base64 encode, etc.
w.Write([]byte("test\n"))
w.Close()
}()
if _, err := io.Copy(os.Stdout, r); err != nil
return err
}

Tlgm: @it_boooks

return nil

}
Создайте новый каталог с именем example и перейдите к нему.
Создайте файл main.go со следующим содержимым:
package main
import (
"bytes"
"fmt"
"github.com/PacktPublishing/
Go-Programming-Cookbook-Second-Edition/
chapter1/bytestrings"
)

{

func main() {
in := bytes.NewReader([]byte("example"))
out := &bytes.Buffer{}
fmt.Print("stdout on Copy = ")
if err := interfaces.Copy(in, out); err != nil
}

panic(err)

fmt.Println("out bytes buffer =", out.String())

{

fmt.Print("stdout on PipeExample = ")
if err := interfaces.PipeExample(); err != nil
panic(err)
}

}
Выполните go run ..
Вы также можете запустить следующее:
$ go build
$ ./example
Вы должны увидеть следующий вывод:
$ go run .
stdout on Copy = exampleexample
out bytes buffer = exampleexample
stdout on PipeExample = test

Tlgm: @it_boooks

Если вы скопировали или написали свои собственные тесты, перейдите
на один каталог вверх и запустите go test и убедитесь, что все тесты
пройдены.

Как это работает…
Функция Copy() копирует байты между интерфейсами и обрабатывает эти
данные как поток. Представление о данных как о потоках имеет множество
практических применений, особенно при работе с сетевым трафиком или
файловыми системами. Функция Copy() также создает интерфейс
MultiWriter, который объединяет два потока записи и дважды записывает в
них с помощью ReadSeeker. Если бы вместо этого использовался интерфейс
Reader, вместо того, чтобы видеть example, вы бы видели только пример,
несмотря на двойное копирование в интерфейс MultiWriter. Вы также
можете использовать буферизованную запись, если ваш поток не
помещается в память.
Структуры PipeReader и PipeWriter реализуют интерфейсы io.Reader и
io.Writer. Они связаны, создавая конвейер в памяти. Основная цель канала
— чтение из потока при одновременной записи из того же потока в другой
источник. По сути, он объединяет два потока в трубу.
Интерфейсы Go — это чистая абстракция для переноса данных, которые
выполняют общие операции. Это становится очевидным при выполнении
операций ввода-вывода, поэтому пакет io — отличный ресурс для изучения
композиции интерфейса. Пакет pipe часто используется недостаточно, но
обеспечивает большую гибкость с безопасностью потоков при связывании
входных и выходных потоков.

Использование пакетов bytes и strings
Пакеты bytes и strings имеют ряд полезных помощников для работы и
преобразования данных из строковых типов в байтовые и наоборот. Они
позволяют создавать буферы, которые работают с рядом общих интерфейсов
ввода/вывода.

Как это сделать…
Следующие шаги описывают, как написать и запустить ваше приложение:
В терминале или консольном приложении создайте новый каталог с
именем ~/projects/go-programmingcookbook/chapter1/bytestrings.

Tlgm: @it_boooks

Перейдите в этот каталог.
Выполните следующую команду:
$ go mod init github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter1/bytestrings
Вы должны увидеть файл с именем go.mod, который содержит
следующее:
module github.com/PacktPublishing/Go-Programming-CookbookSecond-Edition/chapter1/bytestrings
Скопируйте тесты из ~/projects/go-programming-cookbookoriginal/chapter1/bytestrings или используйте это как упражнение
для написания собственного кода!
Создайте файл с именем buffer.go со следующим содержимым:
package bytestrings
import (
"bytes"
"io"
"io/ioutil"
)
bytes

// Buffer demonstrates some tricks for initializing
//Buffers
// These buffers implement an io.Reader interface
func Buffer(rawString string) *bytes.Buffer {

bytes

buffer from

// we'll start with a string encoded into raw
rawBytes := []byte(rawString)
// there are a number of ways to create a
// the raw bytes or from the original string
var b = new(bytes.Buffer)
b.Write(rawBytes)
// alternatively
b = bytes.NewBuffer(rawBytes)
// and avoiding the initial byte array

altogether

Tlgm: @it_boooks

b = bytes.NewBufferString(rawString)
}

return b

// ToString is an example of taking an io.Reader and
consuming
// it all, then returning a string
func toString(r io.Reader) (string, error) {
b, err := ioutil.ReadAll(r)
if err != nil {
return "", err
}
return string(b), nil
}
Создайте файл с именем bytes.go со следующим содержимым:
package bytestrings
import (
"bufio"
"bytes"
"fmt"
)
by the

a byte

// WorkWithBuffer will make use of the buffer created
// Buffer function
func WorkWithBuffer() error {
rawString := "it's easy to encode unicode into
array"
b := Buffer(rawString)

byes with

of

// we can quickly convert a buffer back into
// b.Bytes() or a string with b.String()
fmt.Println(b.String())
// because this is an io Reader we can make use
// generic io reader functions such as
s, err := toString(b)
if err != nil {
return err

Tlgm: @it_boooks

}
fmt.Println(s)
bytes reader
io.ReaderAt,

allows

// we can also take our bytes and create a
// these readers implement io.Reader,
// io.WriterTo, io.Seeker, io.ByteScanner, and
// io.RuneScanner interfaces
reader := bytes.NewReader([]byte(rawString))
// we can also plug it into a scanner that
// buffered reading and tokenzation
scanner := bufio.NewScanner(reader)
scanner.Split(bufio.ScanWords)
// iterate over all of the scan events
for scanner.Scan() {
fmt.Print(scanner.Text())
}

return nil
}
Создайте файл с именем string.go со следующим содержимым:
package bytestrings
import (
"fmt"
"io"
"os"
"strings"
)
// SearchString shows a number of methods
// for searching a string
func SearchString() {
s := "this is a test"
// returns true because s contains
// the word this
fmt.Println(strings.Contains(s, "this"))
// returns true because s contains the letter a
// would also match if it contained b or c

Tlgm: @it_boooks

fmt.Println(strings.ContainsAny(s, "abc"))
// returns true because s starts with this
fmt.Println(strings.HasPrefix(s, "this"))
// returns true because s ends with this
fmt.Println(strings.HasSuffix(s, "test"))
}
// ModifyString modifies a string in a number of ways
func ModifyString() {
s := "simple string"
// prints [simple string]
fmt.Println(strings.Split(s, " "))
// prints "Simple String"
fmt.Println(strings.Title(s))

}

// prints "simple string"; all trailing and
// leading white space is removed
s = " simple string "
fmt.Println(strings.TrimSpace(s))

// StringReader demonstrates how to create
// an io.Reader interface quickly with a string
func StringReader() {
s := "simple stringn"
r := strings.NewReader(s)
// prints s on Stdout
io.Copy(os.Stdout, r)

}
Создайте новый каталог с именем example и перейдите к нему.
Создайте файл main.go со следующим содержимым:
package main
import "github.com/PacktPublishing/
Go-Programming-Cookbook-Second-Edition/
chapter1/bytestrings"
func main() {
err := bytestrings.WorkWithBuffer()

Tlgm: @it_boooks

if err != nil {
panic(err)
}
// each of these print to stdout
bytestrings.SearchString()
bytestrings.ModifyString()
bytestrings.StringReader()

}
Выполните go run ..
Вы также можете запустить следующее:
$ go build
$ ./example
Вы должны увидеть следующий вывод:

$ go run .
it's easy to encode unicode into a byte array ??
it's easy to encode unicode into a byte array ??
it'seasytoencodeunicodeintoabytearray??true
true
true
true
[simple string]
Simple String
simple string
simple string
Если вы скопировали или написали свои собственные тесты, перейдите
на один каталог вверх и запустите go test и убедитесь, что все тесты
пройдены.

Как это работает…
Библиотека bytes предоставляет ряд удобных функций при работе с
данными. Например, буфер гораздо более гибок, чем массив байтов, при
работе с библиотеками или методами потоковой обработки. После того как
вы создали буфер, его можно использовать для удовлетворения требований
интерфейса io.Reader, чтобы вы могли воспользоваться преимуществами
функций ioutil для управления данными. Для потоковых приложений вы,
вероятно, захотите использовать буфер и сканер. В таких случаях
пригодится пакет bufio. Иногда использование массива или среза больше
подходит для небольших наборов данных или когда на вашем компьютере
много памяти.

Tlgm: @it_boooks

Go обеспечивает большую гибкость в преобразовании данных между
интерфейсами при использовании этих базовых типов — преобразование
между строками и байтами относительно простое. При работе со строками
пакет strings предоставляет ряд удобных функций для работы, поиска и
управления строками. В некоторых случаях может подойти хорошее
регулярное выражение, но в большинстве случаев достаточно пакетов
strings и strconv. Пакет strings позволяет сделать строку похожей на
заголовок, разбить ее на массив или обрезать пробелы. Он также
предоставляет собственный интерфейс Reader, который можно использовать
вместо типа чтения пакетов bytes.

Работа с каталогами и файлами
Работа с каталогами и файлами может быть затруднена при переключении
между платформами (например, Windows и Linux). Go обеспечивает
кроссплатформенную поддержку для работы с файлами и каталогами в
пакетах os и ioutils. Мы уже видели примеры ioutils, но теперь мы
рассмотрим, как использовать их по-другому!!

Как это сделать…
Следующие шаги описывают, как написать и запустить ваше приложение:
В терминале или консольном приложении создайте новый каталог с
именем ~/projects/go-programming-cookbook/chapter1/filedirs.
Перейдите в этот каталог.
Выполните следующую команду:
$ go mod init github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter1/filedirs
Вы должны увидеть файл с именем go.mod, который содержит
следующее содержимое:
module github.com/PacktPublishing/Go-Programming-CookbookSecond-Edition/chapter1/filedirs
Скопируйте тесты из ~/projects/go-programming-cookbookoriginal/chapter1/filedirs или используйте это как упражнение для
написания собственного кода!
Создайте файл dirs.go со следующим содержимым:
package filedirs

Tlgm: @it_boooks

import (
"errors"
"io"
"os"
)

Chown

// Operate manipulates files and directories
func Operate() error {
// this 0755 is similar to what you'd see with

director
path

// on a command line this will create a
// /tmp/example, you may also use an absolute

// instead of a relative one
if err := os.Mkdir("example_dir",
os.FileMode(0755));
err != nil {
return err
}
// go to the /tmp directory
if err := os.Chdir("example_dir"); err != nil {
return err
}
//
//
//
//
f,
if

f is a generic file object
it also implements multiple interfaces
and can be used as a reader or writer
if the correct bits are set when opening
err := os.Create("test.txt")
err != nil {
return err

}
and

returned

// we write a known-length value to the file
// validate that it wrote correctly
value := []byte("hellon")
count, err := f.Write(value)
if err != nil {
return err
}
if count != len(value) {
return errors.New("incorrect length

Tlgm: @it_boooks

from write")
}
if err := f.Close(); err != nil {
return err
}
// read the file
f, err = os.Open("test.txt")
if err != nil {
return err
}
io.Copy(os.Stdout, f)
if err := f.Close(); err != nil {
return err
}
// go to the /tmp directory
if err := os.Chdir(".."); err != nil {
return err
}
// cleanup, os.RemoveAll can be dangerous if
you
input,
nil {

// point at the wrong directory, use user
// and especially if you run as root
if err := os.RemoveAll("example_dir"); err !=
}

return err

return nil
}
Создайте файл с именем files.go со следующим содержимым:
package filedirs
import (
"bytes"
"io"
"os"
"strings"
)

Tlgm: @it_boooks

error {

// Capitalizer opens a file, reads the contents,
// then writes those contents to a second file
func Capitalizer(f1 *os.File, f2 *os.File)
if _, err := f1.Seek(0, io.SeekStart); err !=

nil {

return err
}
var tmp = new(bytes.Buffer)
if _, err := io.Copy(tmp, f1); err != nil {
return err
}
s := strings.ToUpper(tmp.String())
if _, err := io.Copy(f2, strings.NewReader(s));

err !=

nil {
return err

}

}
return nil

// CapitalizerExample creates two files, writes to one
//then calls Capitalizer() on both
func CapitalizerExample() error {
f1, err := os.Create("file1.txt")
if err != nil {
return err
}
contains a

if _, err := f1.Write([]byte(`this file
number of words and new lines`)); err != nil {
return err
}
f2, err := os.Create("file2.txt")
if err != nil {
return err
}
if err := Capitalizer(f1, f2); err != nil {

Tlgm: @it_boooks

return err
}
if err := os.Remove("file1.txt"); err != nil {
return err
}
if err := os.Remove("file2.txt"); err != nil {
return err
}
return nil
}
Создайте новый каталог с именем example и перейдите к нему.
Создайте файл main.go со следующим содержимым:
package main
import "github.com/PacktPublishing/
Go-Programming-Cookbook-Second-Edition/
chapter1/filedirs"
func main() {
if err := filedirs.Operate(); err != nil {
panic(err)
}
if err := filedirs.CapitalizerExample(); err !=
nil {

panic(err)

}
}
Выполните go run ..
Вы также можете запустить следующее:
$ go build
$ ./example
Вы должны увидеть следующий вывод:
$ go run .
hello
Если вы скопировали или написали свои собственные тесты, перейдите
на один каталог вверх и запустите go test и убедитесь, что все тесты

Tlgm: @it_boooks

пройдены.

Как это работает…
Если вы знакомы с файлами в Unix, библиотека Go os должна показаться
вам очень знакомой. Вы можете выполнять практически все стандартные
операции — Stat файл для сбора атрибутов, собирать файл с различными
разрешениями, а также создавать и изменять каталоги и файлы. В этом
рецепте мы проделали ряд манипуляций с директориями и файлами и потом
подчистили за собой.
Работа с файловыми объектами очень похожа на работу с потоками в
памяти. Файлы также напрямую предоставляют ряд удобных функций,
таких как Chown, Stat и Truncate. Самый простой способ освоиться с
файлами — использовать их. Во всех предыдущих рецептах мы должны
тщательно убирать за нашими программами.
Работа с файлами — очень распространенная операция при создании
серверных приложений. Файлы можно использовать для конфигурации,
секретных ключей, в качестве временного хранилища и многого другого. Go
оборачивает системные вызовы ОС с помощью пакета os и позволяет
работать одним и тем же функциям независимо от того, используете ли вы
Windows или Unix.
Как только ваш файл открыт и сохранен в структуре File, его можно легко
передать в ряд интерфейсов (мы обсуждали эти интерфейсы ранее). Все
предыдущие примеры могут использовать структуры os.File напрямую
вместо буферов и потоков данных в памяти, чтобы работать с данными,
хранящимися на диске. Это может быть полезно для определенных методов,
таких как одновременная запись всех журналов в stderr и в файл с
помощью одного вызова записи.

Работа с форматом CSV
CSV — это распространенный формат, который используется для
манипулирования данными. Например, часто приходится импортировать или
экспортировать файл CSV в Excel. Пакет Go CSV работает с интерфейсами
данных, поэтому данные легко записывать в буфер, stdout, файл или сокет.
Примеры в этом разделе покажут некоторые распространенные способы
получения данных в формате CSV и из него.

Как это сделать…

Tlgm: @it_boooks

Эти шаги описывают, как написать и запустить ваше приложение:
В терминале или консольном приложении создайте новый каталог с
именем ~/projects/go-programming-cookbook/chapter1/csvformat.
Перейдите в этот каталог.
Выполните следующую команду:
$ go mod init github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter1/csvformat
Вы должны увидеть файл с именем go.mod, который содержит
следующее содержимое:
module github.com/PacktPublishing/Go-Programming-CookbookSecond-Edition/chapter1/csvformat
Скопируйте тесты из ~/projects/go-programming-cookbookoriginal/chapter1/csvformat или используйте это как упражнение
для написания собственного кода!
Создайте файл с именем read_csv.go со следующим содержимым:
package csvformat
import (
"bytes"
"encoding/csv"
"fmt"
"io"
"strconv"
)
// Movie will hold our parsed CSV
type Movie struct {
Title string
Director string
Year int
}
// ReadCSV gives shows some examples of processing CSV
// that is passed in as an io.Reader
func ReadCSV(b io.Reader) ([]Movie, error) {
r := csv.NewReader(b)
options

// These are some optional configuration
r.Comma = ';'

Tlgm: @it_boooks

r.Comment = '-'
var movies []Movie
//
//
dictionary key or
//
_,
if
}

grab and ignore the header for now
we may also want to use this for a
some other form of lookup
err := r.Read()
err != nil && err != io.EOF {
return nil, err

// loop until it's all processed
for {
record, err := r.Read()
if err == io.EOF {
break
} else if err != nil {
return nil, err
}
year, err :=
strconv.ParseInt(record[2], 10,
64)
if err != nil {
return nil, err
}
m := Movie{record[0], record[1],
int(year)}

movies = append(movies, m)
}
return movies, nil

}
Добавьте эту дополнительную функцию в read_csv.go следующим
образом:
// AddMoviesFromText uses the CSV parser with a string
func AddMoviesFromText() error {
// this is an example of us taking a string,
converting
// it into a buffer, and reading it
// with the csv package
in := `

Tlgm: @it_boooks

- first our headers
movie title;director;year released
- then some data
Guardians of the Galaxy Vol. 2;James Gunn;2017
Star Wars: Episode VIII;Rian Johnson;2017
`
b := bytes.NewBufferString(in)
m, err := ReadCSV(b)
if err != nil {
return err
}
fmt.Printf("%#vn", m)
return nil
}
Создайте файл с именем write_csv.go со следующим содержимым:
package csvformat
import (
"bytes"
"encoding/csv"
"io"
"os"
)
// A Book has an Author and Title
type Book struct {
Author string
Title string
}
// Books is a named type for an array of books
type Books []Book
// ToCSV takes a set of Books and writes to an
io.Writer
// it returns any errors
func (books *Books) ToCSV(w io.Writer) error {
n := csv.NewWriter(w)
err := n.Write([]string{"Author", "Title"})
if err != nil {
return err
}
for _, book := range *books {

Tlgm: @it_boooks

}

err := n.Write([]string{book.Author,
book.Title})
if err != nil {
return err
}

n.Flush()
return n.Error()
}
Добавьте эти дополнительные функции в write_csv.go следующим
образом:

Rye",

// WriteCSVOutput initializes a set of books
// and writes the to os.Stdout
func WriteCSVOutput() error {
b := Books{
Book{
Author: "F Scott Fitzgerald",
Title: "The Great Gatsby",
},
Book{
Author: "J D Salinger",
Title: "The Catcher in the
}

},

return b.ToCSV(os.Stdout)
}
// WriteCSVBuffer returns a buffer csv for
// a set of books
func WriteCSVBuffer() (*bytes.Buffer, error) {
b := Books{
Book{
Author: "F Scott Fitzgerald",
Title: "The Great Gatsby",
},
Book{
Author: "J D Salinger",
Title: "The Catcher in the
Rye",

},
}

Tlgm: @it_boooks

w := &bytes.Buffer{}
err := b.ToCSV(w)
return w, err
}
Создайте новый каталог с именем example и перейдите к нему.
Создайте файл main.go со следующим содержимым:
package main
import (
"fmt"
"github.com/PacktPublishing/
Go-Programming-Cookbook-Second-Edition/
chapter1/csvformat"
)

nil {

func main() {
if err := csvformat.AddMoviesFromText(); err !=
}

panic(err)

if err := csvformat.WriteCSVOutput(); err !=
nil {

panic(err)
}
buffer, err := csvformat.WriteCSVBuffer()
if err != nil {
panic(err)
}

fmt.Println("Buffer = ", buffer.String())
}
Выполните go run ..
Вы также можете запустить следующее:
$ go build
$ ./example
Вы должны увидеть следующий вывод:

Tlgm: @it_boooks

$ go run .
[]csvformat.Movie{csvformat.Movie{Title:"Guardians of the
Galaxy Vol. 2", Director:"James Gunn", Year:2017},
csvformat.Movie{Title:"Star Wars: Episode VIII",
Director:"Rian
Johnson", Year:2017}}
Author,Title
F Scott Fitzgerald,The Great Gatsby
J D Salinger,The Catcher in the Rye
Buffer = Author,Title
F Scott Fitzgerald,The Great Gatsby
J D Salinger,The Catcher in the Rye
Если вы скопировали или написали свои собственные тесты, перейдите
на один каталог вверх и запустите go test и убедитесь, что все тесты
пройдены.

Как это работает…
Чтобы научиться читать формат CSV, мы сначала представляем наши
данные в виде структуры. В Go очень полезно форматировать данные как
структуру, поскольку это делает такие вещи, как маршалинг и кодирование,
относительно простыми. В нашем примере чтения в качестве типа данных
используются фильмы. Функция принимает интерфейс io.Reader, который
содержит наши данные CSV в качестве входных данных. Это может быть
файл или буфер. Затем мы используем эти данные для создания и
заполнения структуры Movie, включая преобразование года в целое число.
Мы также добавляем параметры для парсера CSV для использования ;
(точка с запятой) в качестве разделителя и - (дефис) в качестве строки
комментария.
Далее мы исследуем ту же идею, но в обратном порядке. Романы
представлены с названием и автором. Мы инициализируем массив романов,
а затем записываем определенные романы в формате CSV в интерфейс
io.Writer. Опять же, это может быть файл, stdout или буфер.
Пакет CSV — отличный пример того, почему потоки данных в Go можно
рассматривать как реализацию общих интерфейсов. Легко изменить
источник и место назначения наших данных с помощью небольших
однострочных настроек, и мы можем легко манипулировать данными CSV,
не используя чрезмерного объема памяти или времени. Например, можно
было бы читать из потока данных по одной записи за раз и записывать в
отдельный поток в модифицированном формате по одной записи за раз. Это
не приведет к значительному использованию памяти или процессора.

Tlgm: @it_boooks

Позже, когда мы будем изучать конвейеры данных и рабочие пулы, вы
увидите, как эти идеи можно комбинировать и как обрабатывать эти потоки
параллельно.

Работа с временными файлами
Мы уже создали и использовали файлы для ряда примеров. Нам также
приходилось вручную справляться с очисткой, конфликтами имен и многим
другим. Временные файлы и каталоги — более быстрый и простой способ
справиться с такими случаями.

Как это сделать…
Следующие шаги описывают, как написать и запустить ваше приложение:
В терминалеили консольном приложении создайте новый каталог с
именем ~/projects/go-programming-cookbook/chapter1/tempfiles.
Перейдите в этот каталог.
Выполните следующую команду:
$ go mod init github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter1/tempfiles
Вы должны увидеть файл с именем go.mod, который содержит
следующее содержимое:
module github.com/PacktPublishing/Go-Programming-CookbookSecond-Edition/chapter1/tempfiles
Скопируйте тесты из ~/projects/go-programming-cookbookoriginal/chapter1/tempfiles или используйте это как упражнение
для написания собственного кода!
Создайте файл с именем temp_files.go со следующим содержимым:
package tempfiles
import (
"fmt"
"io/ioutil"
"os"
)
// WorkWithTemp will give some basic patterns for
working
// with temporary files and directories

Tlgm: @it_boooks

with

func WorkWithTemp() error {
// If you need a temporary place to store files

directory
argument
directory

// the same name ie. template1-10.html a temp
// is a good way to approach it, the first
// being blank means it will use create the
//
//
t,
if

in the location returned by
os.TempDir()
err := ioutil.TempDir("", "tmp")
err != nil {
return err

}
file
this
to the

// This will delete everything inside the temp
// when this function exits if you want to do
// later, be sure to return the directory name
// calling function
defer os.RemoveAll(t)
// the directory must exist to create the

tempfile
// created. t is an *os.File object.
tf, err := ioutil.TempFile(t, "tmp")
if err != nil {
return err
}
fmt.Println(tf.Name())
here, but
directory, it

// normally we'd delete the temporary file
// because we're placing it in a temp
// gets cleaned up by the earlier defer

return nil
}
Создайте новый каталог с именем example и перейдите к нему.
Создайте файл main.go со следующим содержимым:

Tlgm: @it_boooks

package main
import "github.com/PacktPublishing/
Go-Programming-Cookbook-Second-Edition/
chapter1/tempfiles"

{

func main() {
if err := tempfiles.WorkWithTemp(); err != nil
panic(err)
}

}
Выполните go run ..
Вы также можете запустить следующее:
$ go build
$ ./example
Вы должны увидеть следующий вывод (с другим путем):
$ go run .
/var/folders/kd/ygq5l_0d1xq1lzk_c7htft900000gn/T
/tmp764135258/tmp588787953
Если вы скопировали или написали свои собственные тесты, перейдите
на один каталог вверх и запустите go test и убедитесь, что все тесты
пройдены.

Как это работает…
Создание временных файлов и каталогов можно выполнить с помощью
пакета ioutil. Хотя вы по-прежнему должны удалять файлы
самостоятельно, использование RemoveAll является соглашением, и оно
сделает это за вас всего с одной дополнительной строкой кода.
При написании тестов настоятельно рекомендуется использовать временные
файлы. Это также полезно для таких вещей, как создание артефактов и
многое другое. Пакет Go ioutil по умолчанию попытается учесть
настройки ОС, но при необходимости позволит вам вернуться к другим
каталогам.

Работа с text/template и html/template
Go предоставляет богатую поддержку шаблонов. Легко вкладывать
шаблоны, импортировать функции, представлять переменные, перебирать

Tlgm: @it_boooks

данные и так далее. Если вам нужно что-то более сложное, чем средство
записи CSV, отличным решением могут стать шаблоны.
Еще одно применение шаблонов — для веб-сайтов. Когда мы хотим
отобразить данные на стороне сервера для клиента, шаблоны прекрасно
подходят для этого. Поначалу шаблоны Go могут показаться запутанными. В
этом разделе рассматривается работа с шаблонами, сбор шаблонов внутри
каталога и работа с шаблонами HTML.

Как это сделать…
Эти шаги описывают, как написать и запустить ваше приложение:
В терминале или консольном приложении создайте новый каталог с
именем ~/projects/go-programming-cookbook/chapter1/templates.
Перейдите в этот каталог.
Выполните следующую команду:
$ go mod init github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter1/templates
Вы должны увидеть файл с именем go.mod, который содержит
следующее:
module github.com/PacktPublishing/Go-Programming-CookbookSecond-Edition/chapter1/templates
Скопируйте тесты из ~/projects/go-programming-cookbookoriginal/chapter1/templates или используйте это как упражнение,
чтобы написать свой собственный код!
Создайте файл templates.go со следующим содержимым:
package templates
import (
"os"
"strings"
"text/template"
)
const sampleTemplate = `
This template demonstrates printing a {{
.Variable |
printf "%#v" }}.
{{if .Condition}}

Tlgm: @it_boooks

If condition is set, we'll print this
{{else}}
Otherwise, we'll print this instead
{{end}}
Next we'll iterate over an array of strings:
{{range $index, $item := .Items}}
{{$index}}: {{$item}}
{{end}}
We can also easily import other functions like
strings.Split
then immediately used the array created as a
result:

another

{{ range $index, $item := split .Words ","}}
{{$index}}: {{$item}}
{{end}}
Blocks are a way to embed templates into one
{{ block "block_example" .}}
No Block defined!
{{end}}

`

{{/*
This is a way
to insert a multi-line comment
*/}}
const secondTemplate = `
{{ define "block_example" }}
{{.OtherVariable}}
{{end}}

`
Добавьте функцию в конец templates.go следующим образом:
a

// RunTemplate initializes a template and demonstrates
// variety of template helper functions
func RunTemplate() error {
data := struct {
Condition bool
Variable string
Items []string

Tlgm: @it_boooks

Words string
OtherVariable string
}{

"item3"},

Condition: true,
Variable: "variable",
Items: []string{"item1", "item2",
Words:

"another_item1,another_item2,another_item3",
OtherVariable: "I'm defined in a second
template!",
}
funcmap := template.FuncMap{
"split": strings.Split,
}
// these can also be chained
t := template.New("example")
t = t.Funcs(funcmap)
//
//
t,
if
}
template
a second

We could use Must instead to panic on error
template.Must(t.Parse(sampleTemplate))
err := t.Parse(sampleTemplate)
err != nil {
return err

// to demonstrate blocks we'll create another
// by cloning the first template, then parsing
t2, err := t.Clone()
if err != nil {
return err
}
t2, err = t2.Parse(secondTemplate)
if err != nil {
return err
}
// write the template to stdout and populate it
// with data
err = t2.Execute(os.Stdout, &data)

Tlgm: @it_boooks

if err != nil {
return err
}
return nil
}
Создайте файл с именем template_files.go со следующим
содержимым:
package templates
import (
"io/ioutil"
"os"
"path/filepath"
"text/template"
)
//CreateTemplate will create a template file that
contains data
func CreateTemplate(path string, data string) error {
return ioutil.WriteFile(path, []byte(data),
os.FileMode(0755))
}
// InitTemplates sets up templates from a directory
func InitTemplates() error {
tempdir, err := ioutil.TempDir("", "temp")
if err != nil {
return err
}
defer os.RemoveAll(tempdir)
"t1.tmpl"),

"t2.tmpl"),

err = CreateTemplate(filepath.Join(tempdir,
`Template 1! {{ .Var1 }}
{{ block "template2" .}} {{end}}
{{ block "template3" .}} {{end}}
`)
if err != nil {
return err
}
err = CreateTemplate(filepath.Join(tempdir,

Tlgm: @it_boooks

`{{ define "template2"}}Template 2! {{ .Var2 }}
{{end}}

`)
if err != nil {
return err
}

"t3.tmpl"),
{{end}}

err = CreateTemplate(filepath.Join(tempdir,
`{{ define "template3"}}Template 3! {{ .Var3 }}
`)
if err != nil {
return err
}
pattern := filepath.Join(tempdir, "*.tmpl")

match

// Parse glob will combine all the files that
// glob and combine them into a single template
tmpl, err := template.ParseGlob(pattern)
if err != nil {
return err
}
// Execute can also work with a map instead
// of a struct
tmpl.Execute(os.Stdout, map[string]string{
"Var1": "Var1!!",
"Var2": "Var2!!",
"Var3": "Var3!!",
})

return nil
}
Создайте файл с именем html_templates.go со следующим
содержимым:
package templates
import (
"fmt"
"html/template"
"os"

Tlgm: @it_boooks

)
// HTMLDifferences highlights some of the differences
// between html/template and text/template
func HTMLDifferences() error {
t := template.New("html")
t, err := t.Parse("Hello! {{.Name}}n")
if err != nil {
return err
}
// html/template auto-escapes unsafe operations
like
aware and

// javascript injection this is contextually

// will behave differently
// depending on where a variable is rendered
err = t.Execute(os.Stdout,
map[string]string{"Name": "
alert('Can you see me?')
"})
if err != nil {
return err
}
// you can also manually call the escapers
fmt.Println(template.JSEscaper(`example
`))
fmt.Println(template.HTMLEscaper(`example
`))
fmt.Println(template.URLQueryEscaper(`example
`))
return nil

}
Создайте новый каталог с именем example и перейдите к нему.
Создайте файл main.go со следующим содержимым:
package main
import "github.com/PacktPublishing/
Go-Programming-Cookbook-Second-Edition/
chapter1/templates"
func main() {

Tlgm: @it_boooks

if err := templates.RunTemplate(); err != nil {
panic(err)
}
if err := templates.InitTemplates(); err != nil
{
}
nil {

panic(err)

if err := templates.HTMLDifferences(); err !=
panic(err)
}

}
Выполните go run ..
Вы также можете запустить следующее:
$ go build
$ ./example
Вы должны увидеть следующий вывод (с другим путем):

Tlgm: @it_boooks

Если вы скопировали или написали свои собственные тесты, перейдите
на один каталог вверх и запустите go test и убедитесь, что все тесты
пройдены.

Как это работает…
В Go есть два пакета шаблонов: text/template и html/template. Они
разделяют функциональность и разнообразие функций. В общем, вы
должны использовать html/template для рендеринга веб-сайтов и
text/template для всего остального. Шаблоны представляют собой обычный

Tlgm: @it_boooks

текст, но внутри фигурных скобок можно использовать переменные и
функции.
Пакеты шаблонов также предоставляют удобные методы для работы с
файлами. Пример, который мы использовали здесь, создает несколько
шаблонов во временном каталоге, а затем считывает их все с помощью
одной строки кода.
Пакет html/template является оболочкой пакета text/template. Все
примеры шаблонов работают с пакетом html/template напрямую, без
каких-либо модификаций и только с изменением оператора импорта. HTMLшаблоны обеспечивают дополнительное преимущество контекстнозависимой безопасности; это предотвращает нарушения безопасности, такие
как внедрение JavaScript.
Пакеты шаблонов предоставляют то, что вы ожидаете от современной
библиотеки шаблонов. Легко комбинировать шаблоны, добавлять логику
приложения и обеспечивать безопасность при передаче результатов в HTML
и JavaScript.

Tlgm: @it_boooks

2. Инструменты командной
строки
Приложения командной строки — один из самых простых способов
обработки пользовательского ввода и вывода. В этой главе основное
внимание будет уделено взаимодействиям на основе командной
строки, таким как аргументы командной строки, конфигурация и
переменные среды. Мы закончим библиотекой для раскрашивания
вывода текста в Unix и Bash для Windows.
С помощью рецептов, приведенных в этой главе, вы должны быть
готовы справиться с ожидаемыми и неожиданными действиями
пользователя. Рецепт «Перехват и обработка сигналов» является
примером случаев, когда пользователи могут отправлять неожиданные
сигналы вашему приложению, а рецепт каналов — это хорошая
альтернатива принятию пользовательских входных данных по
сравнению с флагами или аргументами командной строки.
Мы надеемся, что рецепт цвета ANSI предоставит пользователям
несколько примеров очистки вывода. Например, при ведении
журналов возможность раскрашивать текст в зависимости от его
назначения иногда может сделать большие блоки текста значительно
четче.
В этой главе мы рассмотрим следующие рецепты:
Использование флагов командной строки
Использование аргументов командной строки
Чтение и установка переменных среды
Конфигурация с использованием TOML, YAML и JSON
Работа с каналами Unix
Перехват и обработка сигналов
Приложение для раскрашивания ANSI

Технические требования
Чтобы продолжить выполнение всех рецептов в этой главе, настройте
свою среду в соответствии со следующими шагами:

Tlgm: @it_boooks

Загрузите и установите Go 1.12.6 или более позднюю версию в
своей операционной системе по адресу
https://golang.org/doc/install.
Откройте терминал или консольное приложение, создайте и
перейдите в каталог проекта, например ~/projects/goprogramming-cookbook. Весь наш код будет запускаться и
изменяться из этого каталога.
Скопируйте последний код в ~/projects/go-programmingcookbook-original и работайте с этим каталогом, а не вводите
примеры вручную:
$ git clone git@github.com:PacktPublishing/GoProgramming-Cookbook-Second-Edition.git go-programmingcookbook-original

Использование флагов командной
строки
Пакет flag упрощает добавление аргументов флага командной строки
в приложение Go. У него есть несколько недостатков — вы склонны
дублировать большой объем кода, чтобы добавить сокращенные
версии флагов, и они упорядочены в алфавитном порядке из подсказки
справки. Существует ряд сторонних библиотек, которые пытаются
устранить эти недостатки, но в этой главе основное внимание будет
уделено версии стандартной библиотеки, а не этим библиотекам.

Как это сделать…
Эти шаги охватывают написание и запуск вашего приложения:
В терминале или консольном приложении создайте новый каталог
с именем ~/projects/go-programmingcookbook/chapter2/flags.
Перейдите в этот каталог.
Выполните следующую команду:
$ go mod init github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter2/flags

Tlgm: @it_boooks

Вы должны увидеть файл с именем go.mod, который содержит
следующее:
module github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter2/flags
Скопируйте тесты из ~/projects/go-programming-cookbookoriginal/chapter2/flags или используйте это как возможность
написать свой собственный код!
Создайте файл flags.go со следующим содержимым:
package main
import (
"flag"
"fmt"
)
// Config will be the holder for our flags
type Config struct {
subject string
isAwesome bool
howAwesome int
countTheWays CountTheWays
}
// Setup initializes a config from flags that
// are passed in
func (c *Config) Setup() {
// you can set a flag directly like so:
// var someVar = flag.String("flag_name",
"default_val",
// "description")
// but in practice putting it in a struct is
generally
// better longhand
flag.StringVar(&c.subject, "subject", "",
"subject is a
string, it defaults to empty")
// shorthand
flag.StringVar(&c.subject, "s", "", "subject
is a string,
it defaults to empty (shorthand)")

Tlgm: @it_boooks

p y (

) )

flag.BoolVar(&c.isAwesome, "isawesome", false,

"is it

awesome or what?")
flag.IntVar(&c.howAwesome, "howawesome", 10,
"how awesome
out of 10?")
// custom variable type
flag.Var(&c.countTheWays, "c", "comma separated

list of

integers")
}
// GetMessage uses all of the internal
// config vars and returns a sentence
func (c *Config) GetMessage() string {
msg := c.subject
if c.isAwesome {
msg += " is awesome"
} else {
msg += " is NOT awesome"
}

msg = fmt.Sprintf("%s with a certainty of %d
out of 10. Let
me count the ways %s", msg, c.howAwesome,
c.countTheWays.String())
return msg
}
Создайте файл custom.go со следующим содержимым:
package main
import (
"fmt"
"strconv"
"strings"
)
// CountTheWays is a custom type that
// we'll read a flag into

Tlgm: @it_boooks

type CountTheWays []int
func (c *CountTheWays) String() string {
result := ""
for _, v := range *c {
if len(result) > 0 {
result += " ... "
}
result += fmt.Sprint(v)
}
return result
}
// Set will be used by the flag package
func (c *CountTheWays) Set(value string) error {
values := strings.Split(value, ",")
for _, v := range values {
i, err := strconv.Atoi(v)
if err != nil {
return err
}
*c = append(*c, i)
}
return nil

}
Выполните следующую команду:
$ go mod tidy
Создайте файл с именем main.go со следующим содержимым:
package main
import (
"flag"
"fmt"
)
func main() {
// initialize our setup
c := Config{}

Tlgm: @it_boooks

c.Setup()
// generally call this from main
flag.Parse()
fmt.Println(c.GetMessage())

}
Выполните следующие команды в командной строке:
$ go build
$ ./flags -h
Попробуйте эти и некоторые другие аргументы; вы должны
увидеть следующий вывод:
$ go build
$ ./flags -h
Usage of ./flags:
-c value
comma separated list of integers
-howawesome int
how awesome out of 10? (default 10)
-isawesome
is it awesome or what? (default false)
-s string
subject is a string, it defaults to empty (shorthand)
-subject string
subject is a string, it defaults to empty
$ ./flags -s Go -isawesome -howawesome 10 -c 1,2,3
Go is awesome with a certainty of 10 out of 10. Let me
count
the ways 1 ... 2 ... 3
Если вы скопировали или написали свои собственные тесты,
перейдите на один каталог вверх и запустите go test и убедитесь,
что все тесты пройдены.

Как это работает…
Этот
рецепт
пытается
продемонстрировать
большинство
распространенных способов использования пакета flag. Он
показывает пользовательские типы переменных, множество

Tlgm: @it_boooks

встроенных переменных, сокращенные флаги и запись всех флагов в
общую структуру. Это первый рецепт, требующий функции main, так
как основное использование флага (flag.Parse()) должно вызываться
из main. В результате нормальный каталог примера опущен.
Пример использования этого приложения показывает, что вы
получаете -h автоматически, чтобы получить список включенных
флагов. Некоторые другие вещи, на которые следует обратить
внимание, это логические флаги, которые вызываются без аргументов,
и что порядок флагов не имеет значения.
Пакет flag — это быстрый способ структурировать входные данные
для приложений командной строки и предоставить гибкие средства
для указания предварительного пользовательского ввода для таких
вещей, как настройка уровней ведения журнала или детализация
приложения. В рецепте «Использование аргументов командной
строки» мы рассмотрим наборы флагов и переключаемся между ними
с помощью аргументов.

Использование аргументов
командной строки
Флаги из предыдущего рецепта являются типом аргумента командной
строки. В этой главе будут рассмотрены другие варианты
использования этих аргументов путем создания команды,
поддерживающей вложенные подкоманды. Это продемонстрирует
наборы флагов, а также использует позиционные аргументы, которые
передаются в ваше приложение.
Как и в предыдущем рецепте, для этого требуется основная функция.
Существует ряд сторонних пакетов, которые имеют дело со сложными
вложенными аргументами и флагами, но мы рассмотрим, как это
сделать, используя только стандартную библиотеку.

Как это сделать…
Эти шаги охватывают написание и запуск вашего приложения:
В терминале или консольном приложении создайте новый каталог
с именем ~/projects/go-programming-

Tlgm: @it_boooks

cookbook/chapter2/cmdargs.
Перейдите в этот каталог.
Выполните следующую команду:
$ go mod init github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter2/cmdargs
Вы должны увидеть файл с именем go.mod, который содержит
следующее:
module github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter2/cmdargs
Скопируйте тесты из Скопируйте тесты из ~/projects/goprogramming-cookbook-original/chapter2/cmdargs или
используйте это как возможность написать свой собственный код!
или используйте это как возможность написать свой собственный
код!
Создайте файл cmdargs.go со следующим содержимым:
package main
import (
"flag"
"fmt"
"os"
)
const version = "1.0.0"
const usage = `Usage:
%s [command]
Commands:
Greet
Version
`
const greetUsage = `Usage:
%s greet name [flag]
Positional Arguments:
name
the name to greet
Flags:
`
// MenuConf holds all the levels
// for a nested cmd line argument

Tlgm: @it_boooks

type MenuConf struct {
Goodbye bool
}
// SetupMenu initializes the base flags
func (m *MenuConf) SetupMenu() *flag.FlagSet {
menu := flag.NewFlagSet("menu",
flag.ExitOnError)
menu.Usage = func() {
fmt.Printf(usage, os.Args[0])
menu.PrintDefaults()
}
return menu
}
// GetSubMenu return a flag set for a submenu
func (m *MenuConf) GetSubMenu() *flag.FlagSet {
submenu := flag.NewFlagSet("submenu",
flag.ExitOnError)
submenu.BoolVar(&m.Goodbye, "goodbye", false,
"Say goodbye
instead of hello")
submenu.Usage = func() {
fmt.Printf(greetUsage, os.Args[0])
submenu.PrintDefaults()
}
return submenu
}
// Greet will be invoked by the greet command
func (m *MenuConf) Greet(name string) {
if m.Goodbye {
fmt.Println("Goodbye " + name + "!")
} else {
fmt.Println("Hello " + name + "!")
}
}
// Version prints the current version that is
// stored as a const
func (m *MenuConf) Version() {
fmt.Println("Version: " + version)
}
Создайте файл с именем main.go со следующим содержимым:

Tlgm: @it_boooks

package main
import (
"fmt"
"os"
"strings"
)
func main() {
c := MenuConf{}
menu := c.SetupMenu()
if err := menu.Parse(os.Args[1:]); err != nil {
fmt.Printf("Error parsing params %s, error:
%v", os.Args[1:], err)
return
}

{

// we use arguments to switch between commands
// flags are also an argument
if len(os.Args) > 1 {
// we don't care about case
switch strings.ToLower(os.Args[1]) {
case "version":
c.Version()
case "greet":
f := c.GetSubMenu()
if len(os.Args) < 3 {
f.Usage()
return
}
if len(os.Args) > 3 {
if err := f.Parse(os.Args[3:]); err != nil

fmt.Fprintf(os.Stderr, "Error parsing
params %s, error: %v", os.Args[3:], err)
return
}
}
c.Greet(os.Args[2])
default:

Tlgm: @it_boooks

fmt.Println("Invalid command")
menu.Usage()
return

}
} else {
menu.Usage()
return
}

}
Выполните go build.
Запустите следующие команды и попробуйте несколько других
комбинаций аргументов:
$ ./cmdargs -h
Usage:
./cmdargs [command]
Commands:
Greet
Version
$./cmdargs version
Version: 1.0.0
$./cmdargs greet
Usage:
./cmdargs greet name [flag]
Positional Arguments:
name
the name to greet
Flags:
-goodbye
Say goodbye instead of hello
$./cmdargs greet reader
Hello reader!

Tlgm: @it_boooks

$./cmdargs greet reader -goodbye
Goodbye reader!
Если вы скопировали или написали свои собственные тесты,
перейдите на один каталог вверх и запустите go test и убедитесь,
что все тесты пройдены.

Как это работает…
Наборы флагов можно использовать для настройки независимых
списков ожидаемых аргументов, строк использования и многого
другого. Разработчик должен выполнить проверку ряда аргументов,
анализируя правильное подмножество аргументов для команд и
определяя строки использования. Это может быть подвержено
ошибкам и требует много итераций, чтобы получить все правильно.
Пакет flag значительно упрощает синтаксический анализ аргументов
и включает удобные методы для получения количества флагов,
аргументов и многого другого. Этот рецепт демонстрирует основные
способы создания сложного приложения командной строки с
использованием аргументов, включая конфигурацию на уровне пакета,
требуемые позиционные аргументы, использование многоуровневых
команд и то, как при необходимости разделить эти вещи на несколько
файлов или пакетов.

Чтение и установка переменных
среды
Переменные среды — это еще один способ передачи состояния в
приложение помимо чтения данных из файла или явной передачи их
через командную строку. Этот рецепт исследует некоторые очень
простые способы получения и установки переменных среды, а затем
поработает с очень полезной сторонней библиотекой envconfig (https://
github.com/kelseyhightower/envconfig).
Мы создадим приложение, которое может читать файл config через
JSON или через переменные среды. В следующем рецепте будут
рассмотрены альтернативные форматы, включая TOML и YAML.

Как это сделать…

Tlgm: @it_boooks

Эти шаги охватывают написание и запуск вашего приложения:
В терминале или консольном приложении создайте новый каталог
с именем ~/projects/go-programmingcookbook/chapter2/envvar.
Перейдите в этот каталог.
Выполните следующую команду:
$ go mod init github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter2/envvar
Вы должны увидеть файл с именем go.mod, который содержит
следующее:
module github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter2/envvar
Скопируйте тесты из ~/projects/go-programming-cookbookoriginal/chapter2/envvar или используйте это как возможность
написать свой собственный код!
Создайте файл с именем config.go со следующим содержимым:
package envvar
import (
"encoding/json"
"os"

)

"github.com/kelseyhightower/envconfig"
"github.com/pkg/errors"

// LoadConfig will load files optionally from the
json file
// stored at path, then will override those values
based on the
// envconfig struct tags. The envPrefix is how we
prefix our
// environment variables.
func LoadConfig(path, envPrefix string, config
interface{})
error {
if path != "" {

Tlgm: @it_boooks

err := LoadFile(path, config)
if err != nil {
return errors.Wrap(err, "error loading
config from

file")
}

from env")
}

}
err := envconfig.Process(envPrefix, config)
return errors.Wrap(err, "error loading config

// LoadFile unmarshalls a json file into a config
struct

func LoadFile(path string, config interface{})

error {
configFile, err := os.Open(path)
if err != nil {
return errors.Wrap(err, "failed to read
config file")
}
defer configFile.Close()
decoder := json.NewDecoder(configFile)
if err = decoder.Decode(config); err != nil {
return errors.Wrap(err, "failed to decode
config file")
}
return nil
}
Создайте новый каталог с именем example и перейдите к нему.
Создайте файл с именем main.go со следующим содержимым:
package main
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"github.com/PacktPublishing/

Tlgm: @it_boooks

Go-Programming-Cookbook-Second-Edition/
chapter2/envvar"
)
// Config will hold the config we
// capture from a json file and env vars
type Config struct {
Version string `json:"version"
required:"true"`
IsSafe bool `json:"is_safe" default:"true"`
Secret string `json:"secret"`
}
func main() {
var err error
// create a temporary file to hold
// an example json file
tf, err := ioutil.TempFile("", "tmp")
if err != nil {
panic(err)
}
defer tf.Close()
defer os.Remove(tf.Name())
// create a json file to hold
// our secrets
secrets := `{
"secret": "so so secret"
}`
if _, err =
tf.Write(bytes.NewBufferString(secrets).Bytes());
err != nil {
panic(err)
}
// We can easily set environment variables
// as needed
if err = os.Setenv("EXAMPLE_VERSION",
"1.0.0"); err != nil
{

Tlgm: @it_boooks

panic(err)
}
if err = os.Setenv("EXAMPLE_ISSAFE", "false");
err != nil {
panic(err)
}
c := Config{}
if err = envvar.LoadConfig(tf.Name(),
"EXAMPLE", &c);
err != nil {
panic(err)
}
secrets)

fmt.Println("secrets file contains =",
// We can also read them
fmt.Println("EXAMPLE_VERSION =",
os.Getenv("EXAMPLE_VERSION"))
fmt.Println("EXAMPLE_ISSAFE =",
os.Getenv("EXAMPLE_ISSAFE"))
// The final config is a mix of json and

environment

// variables
fmt.Printf("Final Config: %#v\n", c)

}
Выполните go run main.go.
Вы также можете запустить следующие команды:
go build
./example
Вы должны увидеть следующий вывод:
$ go run main.go
secrets file contains = {
"secret": "so so secret"
}
EXAMPLE_VERSION = 1.0.0
EXAMPLE_ISSAFE = false

Tlgm: @it_boooks

Final Config: main.Config{Version:"1.0.0", IsSafe:false,
Secret:"so so secret"}
Файл go.mod может быть обновлен, и теперь файл go.sum должен
присутствовать в каталоге рецептов верхнего уровня.
Если вы скопировали или написали свои собственные тесты,
перейдите на один каталог вверх и запустите go test и убедитесь,
что все тесты пройдены.

Как это работает…
Чтение и запись переменных среды с пакетом os довольно просто.
Сторонняя библиотека envconfig, которую использует этот рецепт, —
это умный способ захвата переменных среды и указания определенных
требований с помощью тегов struct.
Функция LoadConfig — это гибкий способ получения информации о
конфигурации из различных источников без больших накладных
расходов или слишком большого количества дополнительных
зависимостей. Было бы просто преобразовать основной config в
другой формат, кроме JSON, или просто всегда использовать
переменные среды.
Также обратите внимание на использование ошибок. В этом рецепте
мы обернули ошибки по всему коду, чтобы мы могли аннотировать
ошибки, не теряя исходной информации об ошибке. Подробнее об
этом будет рассказано в Главе 4, Обработка ошибок в Go.

Конфигурация с использованием
TOML, YAML и JSON
Go
поддерживает
множество
форматов
конфигурации
с
использованием сторонних библиотек. Три самых популярных
формата данных — это TOML, YAML и JSON. Go может
поддерживать JSON «из коробки», а у других есть подсказки о том, как
marshal/unmarshal или encode/decode данные для этих форматов. Эти
форматы имеют много преимуществ помимо конфигурации, но в этой
главе основное внимание будет уделено преобразованию структуры Go
в форму структуры конфигурации. В этом рецепте будут рассмотрены
основные операции ввода и вывода с использованием этих форматов.

Tlgm: @it_boooks

Эти форматы также предоставляют интерфейс, с помощью которого
Go и приложения, написанные на других языках, могут использовать
одну и ту же конфигурацию. Также есть ряд инструментов, которые
работают с этими форматами и упрощают работу с ними.

Как это сделать…
Эти шаги охватывают написание и запуск вашего приложения:
В терминале или консольном приложении создайте новый каталог
с именем ~/projects/go-programmingcookbook/chapter2/confformat.
Перейдите в этот каталог.
Выполните следующую команду:
$ go mod init github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter2/confformat
Вы должны увидеть файл с именем go.mod, который содержит
следующее:
module github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter2/confformat
Скопируйте тесты из ~/projects/go-programming-cookbookoriginal/chapter2/confformat или используйте это как
возможность написать свой собственный код!
Создайте файл с именем toml.go со следующим содержимым:
package confformat
import (
"bytes"
"github.com/BurntSushi/toml"
)
// TOMLData is our common data struct
// with TOML struct tags
type TOMLData struct {
Name string `toml:"name"`
Age int `toml:"age"`
}

Tlgm: @it_boooks

// ToTOML dumps the TOMLData struct to
// a TOML format bytes.Buffer
func (t *TOMLData) ToTOML() (*bytes.Buffer, error)
{

b := &bytes.Buffer{}
encoder := toml.NewEncoder(b)
if err := encoder.Encode(t); err != nil {
return nil, err
}
return b, nil
}

// Decode will decode into TOMLData
func (t *TOMLData) Decode(data []byte)
(toml.MetaData, error) {
return toml.Decode(string(data), t)
}
Создайте файл yaml.go со следующим содержимым:
package confformat
import (
"bytes"
)

"github.com/go-yaml/yaml"

// YAMLData is our common data struct
// with YAML struct tags
type YAMLData struct {
Name string `yaml:"name"`
Age int `yaml:"age"`
}
// ToYAML dumps the YAMLData struct to
// a YAML format bytes.Buffer
func (t *YAMLData) ToYAML() (*bytes.Buffer, error)
{

d, err := yaml.Marshal(t)
if err != nil {

Tlgm: @it_boooks

return nil, err
}
b := bytes.NewBuffer(d)
}

return b, nil

// Decode will decode into TOMLData
func (t *YAMLData) Decode(data []byte) error {
return yaml.Unmarshal(data, t)
}
Создайте файл json.go со следующим содержимым:
package confformat
import (
"bytes"
"encoding/json"
"fmt"
)
// JSONData is our common data struct
// with JSON struct tags
type JSONData struct {
Name string `json:"name"`
Age int `json:"age"`
}

{

// ToJSON dumps the JSONData struct to
// a JSON format bytes.Buffer
func (t *JSONData) ToJSON() (*bytes.Buffer, error)
d, err := json.Marshal(t)
if err != nil {
return nil, err
}
b := bytes.NewBuffer(d)
return b, nil
}

Tlgm: @it_boooks

// Decode will decode into JSONData
func (t *JSONData) Decode(data []byte) error {
return json.Unmarshal(data, t)
}
// OtherJSONExamples shows ways to use types
// beyond structs and other useful functions
func OtherJSONExamples() error {
res := make(map[string]string)
err := json.Unmarshal([]byte(`{"key":
"value"}`), &res)
if err != nil {
return err
}
fmt.Println("We can unmarshal into a map
instead of a
struct:", res)
b := bytes.NewReader([]byte(`{"key2":
"value2"}`))
decoder := json.NewDecoder(b)
if err := decoder.Decode(&res); err != nil {
return err
}
fmt.Println("we can also use decoders/encoders
to work with
streams:", res)
return nil
}
Создайте файл marshal.go со следующим содержимым:
package confformat
import "fmt"
// MarshalAll takes some data stored in structs
// and converts them to the various data formats

Tlgm: @it_boooks

func MarshalAll() error {
t := TOMLData{
Name: "Name1",
Age: 20,
}
j := JSONData{
Name: "Name2",
Age: 30,
}
y := YAMLData{
Name: "Name3",
Age: 40,
}
tomlRes, err := t.ToTOML()
if err != nil {
return err
}
fmt.Println("TOML Marshal =",
tomlRes.String())
jsonRes, err := j.ToJSON()
if err != nil {
return err
}
fmt.Println("JSON Marshal=", jsonRes.String())
yamlRes, err := y.ToYAML()
if err != nil {
return err
}
fmt.Println("YAML Marshal =",
yamlRes.String())
return nil
}
Создайте файл unmarshal.go со следующим содержимым:

Tlgm: @it_boooks

package confformat
import "fmt"
const (
exampleTOML = `name="Example1"
age=99
`
exampleJSON = `{"name":"Example2","age":98}`
exampleYAML = `name: Example3
age: 97
`
)
// UnmarshalAll takes data in various formats
// and converts them into structs
func UnmarshalAll() error {
t := TOMLData{}
j := JSONData{}
y := YAMLData{}
if _, err := t.Decode([]byte(exampleTOML));
err != nil {
return err
}
fmt.Println("TOML Unmarshal =", t)
if err := j.Decode([]byte(exampleJSON)); err
!= nil {

return err
}
fmt.Println("JSON Unmarshal =", j)

!= nil {

if err := y.Decode([]byte(exampleYAML)); err
return err

}
fmt.Println("Yaml Unmarshal =", y)
return nil
}
Создайте новый каталог с именем example и перейдите к нему.
Создайте файл main.go со следующим содержимым:
package main
import "github.com/PacktPublishing/

Tlgm: @it_boooks

Go-Programming-Cookbook-Second-Edition/
chapter2/confformat"
func main() {
if err := confformat.MarshalAll(); err != nil
{
}

panic(err)

if err := confformat.UnmarshalAll(); err !=
nil {
}
!= nil {

panic(err)

if err := confformat.OtherJSONExamples(); err
}

panic(err)

}
Выполните go run main.go.
Вы также можете запустить следующие команды:
$ go build
$ ./example
Вы должны увидеть следующий вывод:
$ go run main.go
TOML Marshal = name = "Name1"
age = 20
JSON Marshal= {"name":"Name2","age":30}
YAML Marshal = name: Name3
age: 40
TOML Unmarshal = {Example1 99}
JSON Unmarshal = {Example2 98}
Yaml Unmarshal = {Example3 97}
We can unmarshal into a map instead of a struct:
map[key:value]
we can also use decoders/encoders to work with streams:
map[key:value key2:value2]

Tlgm: @it_boooks

Файл go.mod может быть обновлен, и теперь файл go.sum должен
присутствовать в каталоге рецептов верхнего уровня.
Если вы скопировали или написали свои собственные тесты,
перейдите на один каталог вверх и запустите go test. Убедитесь,
что все тесты пройдены.

Как это работает…
Этот рецепт предоставил нам примеры того, как использовать
синтаксические анализаторы TOML, YAML и JSON как для записи
необработанных данных в структуру go, так и для чтения данных из
нее в соответствующий формат. Как и в рецептах из Главы 1, «Вводвывод и файловые системы», мы увидели, как часто приходится
быстро переключаться между []byte, string, bytes.Buffer и
другими интерфейсами ввода-вывода.
Пакет encoding/json является наиболее полным в обеспечении
кодирования, маршалинга и других методов для работы с форматом
JSON. Мы абстрагировали их с помощью наших функций ToFormat, и
было бы очень просто присоединить несколько методов, таких как
этот, чтобы мы могли использовать единую структуру, которую можно
быстро преобразовать в любой из этих типов или из него.
Этот рецепт также касается тегов структуры и их использования. В
предыдущей главе они также использовались, и это обычный способ
дать подсказки пакетам и библиотекам о том, как обрабатывать
данные, содержащиеся в структуре.

Работа с каналами Unix
Каналы Unix полезны, когда мы передаем вывод одной программы на
ввод другой. Например, взгляните на следующий код:
$ echo "test case" | wc -l
1
В приложении Go левая часть канала может быть прочитана с
помощью os.Stdin, который действует как файловый дескриптор.
Чтобы продемонстрировать это, этот рецепт примет ввод с левой

Tlgm: @it_boooks

стороны канала и вернет список слов и их количество вхождений. Эти
слова будут токенизированы на пустом пространстве.

Как это сделать…
Эти шаги охватывают написание и запуск вашего приложения:
В терминале или консольном приложении создайте новый каталог
с именем ~/projects/go-programmingcookbook/chapter2/pipes.
Перейдите в этот каталог.
Выполните следующую команду:
$ go mod init github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter2/pipes
Вы должны увидеть файл с именем go.mod, который содержит
следующее:
module github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter2/pipes
Скопируйте тесты из ~/projects/go-programming-cookbookoriginal/chapter2/pipes или используйте это как возможность
написать свой собственный код!
Создайте файл с именем pipe.go со следующим содержимым:
package main
import (
"bufio"
"fmt"
"io"
"os"
)
// WordCount takes a file and returns a map
// with each word as a key and it's number of
// appearances as a value
func WordCount(f io.Reader) map[string]int {
result := make(map[string]int)
// make a scanner to work on the file

Tlgm: @it_boooks

// io.Reader interface
scanner := bufio.NewScanner(f)
scanner.Split(bufio.ScanWords)
for scanner.Scan() {
result[scanner.Text()]++
}
if err := scanner.Err(); err != nil {
fmt.Fprintln(os.Stderr, "reading input:",
err)

}
}

return result

func main() {
fmt.Printf("string:
number_of_occurrences\n\n")
for key, value := range WordCount(os.Stdin) {
fmt.Printf("%s: %d\n", key, value)
}
}
Выполните echo "some string" | go run pipes.go.
Вы также можете запустить следующие команды:
$ go build
echo "some string" | ./pipes
Вы должны увидеть следующий вывод:
$ echo "test case" | go run pipes.go
string: number_of_occurrences
test: 1
case: 1
$ echo "test case test" | go run pipes.go
string: number_of_occurrences
test: 2
case: 1

Tlgm: @it_boooks

Если вы скопировали или написали свои собственные тесты,
перейдите на один каталог вверх и запустите go test. Убедитесь,
что все тесты пройдены.

Как это работает…
Работать с каналами в Go довольно просто, особенно если вы знакомы
с работой с файлами. Например, вы можете использовать рецепт
конвейера из Главы 1, «Ввод-вывод и файловые системы», чтобы
создать приложение tee (https://en.wikipedia.org/wiki/Tee_(command)),
где все, что передается по конвейеру, немедленно записывается в
stdout и в файл.
В этом рецепте используется сканер для токенизации интерфейса
io.Reader файлового объекта os.Stdin. Вы можете видеть, как вы
должны проверять наличие ошибок после завершения всех операций
чтения.

Перехват и обработка сигналов
Сигналы — это полезный способ для пользователя или ОС убить
запущенное приложение. Иногда имеет смысл обрабатывать эти
сигналы более изящно, чем поведение по умолчанию. Go
предоставляет механизм для захвата и обработки сигналов. В этом
рецепте мы рассмотрим обработку сигналов с помощью сигнала,
который обрабатывает горутины.

Как это сделать…
Эти шаги охватывают написание и запуск вашего приложения:
В терминале или консольном приложении создайте новый каталог
с именем ~/projects/go-programmingcookbook/chapter2/signals.
Перейдите в этот каталог.
Выполните следующую команду:
$ go mod init github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter2/signals

Tlgm: @it_boooks

Вы должны увидеть файл с именем go.mod, который содержит
следующее:
module github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter2/signals
Скопируйте тесты из ~/projects/go-programming-cookbookoriginal/chapter2/signals или используйте это как
возможность написать свой собственный код!
Создайте файл signal.go со следующим содержимым:
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
)
// CatchSig sets up a listener for
// SIGINT interrupts
func CatchSig(ch chan os.Signal, done chan bool) {
// block on waiting for a signal
sig := = v
}
}
Создайте новый каталог с именем example и перейдите к нему.
Создайте файл с именем main.go со следующим содержимым:
package main

Tlgm: @it_boooks

import (
"fmt"

)

"github.com/PacktPublishing/
Go-Programming-Cookbook-Second-Edition/
chapter3/collections"

func main() {
ws := []collections.WorkWith{
collections.WorkWith{"Example", 1},
collections.WorkWith{"Example 2", 2},
}
fmt.Printf("Initial list: %#v\n", ws)
// first lower case the list
ws = collections.Map(ws,
collections.LowerCaseData)
fmt.Printf("After LowerCaseData Map: %#v\n",
ws)
// next increment all versions
ws = collections.Map(ws,
collections.IncrementVersion)
fmt.Printf("After IncrementVersion Map:
%#v\n", ws)
// lastly remove all versions older than 3
ws = collections.Filter(ws,
collections.OldVersion(3))
fmt.Printf("After OldVersion Filter: %#v\n",
ws)
}
Выполните go run main.go. Вы также можете запустить
следующее:
$ go build
$ ./example
Вы должны увидеть следующий вывод:

Tlgm: @it_boooks

$ go run main.go
Initial list:
[]collections.WorkWith{collections.WorkWith{Data:"Example
",
Version:1}, collections.WorkWith{Data:"Example 2",
Version:2}}
After LowerCaseData Map:
[]collections.WorkWith{collections.WorkWith{Data:"example
",
Version:1}, collections.WorkWith{Data:"example 2",
Version:2}}
After IncrementVersion Map:
[]collections.WorkWith{collections.WorkWith{Data:"example
",
Version:2}, collections.WorkWith{Data:"example 2",
Version:3}}
After OldVersion Filter:
[]collections.WorkWith{collections.WorkWith{Data:"example
2",
Version:3}}
Если вы скопировали или написали свои собственные тесты,
перейдите на один каталог вверх и запустите go test. Убедитесь,
что все тесты пройдены.

Как это работает…
Замыкания в Go очень эффективны. Хотя наши функции collections
не являются универсальными, они относительно малы и могут быть
легко применены к нашей структуре WorkWith с минимальным
добавленным кодом с использованием различных функций. Глядя на
это, вы можете заметить, что мы нигде не возвращаем ошибки. Идея
этих функций в том, что они чисты: у исходного списка нет побочных
эффектов, за исключением того, что мы решили перезаписывать его
после каждого вызова.
Если вам нужно применить уровни модификации к списку или
структуре списков, то этот шаблон может избавить вас от путаницы и
сделать тестирование очень простым. Также возможно связать карты и
фильтры вместе для очень выразительного стиля кодирования.

Tlgm: @it_boooks

4. Обработка ошибок в Go
Обработка ошибок важна даже для самой простой программы Go.
Ошибки в Go реализуют интерфейс Error и должны обрабатываться
на каждом уровне кода. Ошибки Go не работают как исключения, а
необработанные ошибки могут вызвать огромные проблемы. Вы
должны стремиться обрабатывать и учитывать ошибки всякий раз,
когда они происходят.
В этой главе также рассматривается ведение журнала, так как обычно
журнал регистрируется всякий раз, когда возникает фактическая
ошибка. Мы также исследуем ошибки переноса, чтобы данная ошибка
предоставляла дополнительный контекст по мере того, как она
возвращалась вверх по стеку функций, чтобы было легче определить
фактическую причину определенных ошибок.
В этой главе будут рассмотрены следующие рецепты:
Обработка ошибок и интерфейс ошибок
Использование пакета pkg/errors и перенос ошибок
Использование пакета log и понимание того, когда следует
регистрировать ошибки
Структурированное ведение журналов с помощью пакетов apex и
logrus
Ведение журнала с пакетом context
Использование глобальных переменных уровня пакета
Захват паники для долго выполняющихся процессов

Технические требования
Чтобы продолжить выполнение всех рецептов в этой главе, настройте
свою среду в соответствии со следующими шагами:
Загрузите и установите Go 1.12.6 или более позднюю версию в
своей операционной системе по адресу
https://golang.org/doc/install.
Откройте терминал или консольное приложение, создайте и
перейдите в каталог проекта, например ~/projects/go-

Tlgm: @it_boooks

programming-cookbook. Весь наш код будет запускаться и
изменяться из этого каталога.
Скопируйте последний код в ~/projects/go-programmingcookbook-original и, при желании, работайте из этого каталога,
вместо того, чтобы вводить примеры вручную:
$ git clone git@github.com:PacktPublishing/GoProgramming-Cookbook-Second-Edition.git go-programmingcookbook-original

Обработка ошибок и интерфейс
ошибок
Интерфейс Error — довольно маленький и простой интерфейс:
type Error interface{
Error() string
}
Этот интерфейс элегантен, потому что легко сделать что угодно, чтобы
удовлетворить его. К сожалению, это также создает путаницу для
пакетов, которым необходимо выполнять определенные действия в
зависимости от полученной ошибки.
В Go есть несколько способов создать ошибку; этот рецепт исследует
создание основных ошибок, ошибок, которым присвоены значения или
типы, и пользовательской ошибки с использованием структуры.

Как это сделать…
Эти шаги охватывают написание и запуск вашего приложения:
В терминале или консольном приложении создайте новый каталог
с именем ~/projects/go-programmingcookbook/chapter4/basicerrors and Перейдите в этот каталог.
Выполните следующую команду:
$ go mod init github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter4/basicerrors

Tlgm: @it_boooks

Вы должны увидеть файл с именем go.mod, который содержит
следующее:
module github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter4/basicerrors
Скопируйте тесты из Скопируйте тесты из ~/projects/goprogramming-cookbook-original/chapter4/basicerrors или
используйте это как упражнение для написания собственного
кода! или используйте это как упражнение для написания
собственного кода!
Создайте файл с именем basicerrors.go со следующим
содержимым:
package basicerrors
import (
"errors"
"fmt"
)
// ErrorValue is a way to make a package level
// error to check against. I.e. if err == ErrorValue
var ErrorValue = errors.New("this is a typed error")
// TypedError is a way to make an error type
// you can do err.(type) == ErrorValue
type TypedError struct {
error
}
//BasicErrors demonstrates some ways to create errors
func BasicErrors() {
err := errors.New("this is a quick and easy way to
create an error")
fmt.Println("errors.New: ", err)
err = fmt.Errorf("an error occurred: %s", "something")
fmt.Println("fmt.Errorf: ", err)
err = ErrorValue
fmt.Println("value error: ", err)

Tlgm: @it_boooks

err = TypedError{errors.New("typed error")}
fmt.Println("typed error: ", err)
}
Создайте файл custom.go со следующим содержимым:
package basicerrors
import (
"fmt"
)
// CustomError is a struct that will implement
// the Error() interface
type CustomError struct {
Result string
}
func (c CustomError) Error() string {
return fmt.Sprintf("there was an error; %s was the
result", c.Result)
}
// SomeFunc returns an error
func SomeFunc() error {
c := CustomError{Result: "this"}
return c
}
Создайте новый каталог с именем example и перейдите к нему.
Создайте файл с именем main.go со следующим содержимым:
package main
import (
"fmt"

)

"github.com/PacktPublishing/
Go-Programming-Cookbook-Second-Edition/
chapter4/basicerrors"

Tlgm: @it_boooks

func main() {
basicerrors.BasicErrors()
err := basicerrors.SomeFunc()
fmt.Println("custom error: ", err)

}
Выполните go run main.go.
Вы также можете запустить следующие команды:
$ go build
$ ./example
Вы также можете запустить следующие команды:

$ go run main.go
errors.New: this is a quick and easy way to create an
error
fmt.Errorf: an error occurred: something
typed error: this is a typed error
custom error: there was an error; this was the result
Если вы скопировали или написали свои собственные тесты,
перейдите на один каталог вверх и запустите go test. Убедитесь,
что все тесты пройдены.

Как это работает…
Независимо от того, используете ли вы error.New, fmt.Errorf или
пользовательскую ошибку, важно помнить, что вы никогда не должны
оставлять ошибки в своем коде необработанными. Эти различные
методы определения ошибок дают большую гибкость. Вы можете,
например, поместить дополнительные функции в свою структуру для
дальнейшего опроса ошибки и привести интерфейс к вашему типу
ошибки в вызывающей функции, чтобы получить некоторые
дополнительные функции.
Сам интерфейс очень прост, и единственное требование состоит в том,
чтобы вы возвращали допустимую строку. Соединение этого со
структурой может быть полезно для некоторых высокоуровневых
приложений, которые имеют единую обработку ошибок во всем, но
хотят хорошо работать с другими приложениями.

Tlgm: @it_boooks

Использование пакета pkg/errors и
перенос ошибок
Пакет errors, расположенный на github.com/pkg/errors, является
заменой стандартного пакета errors Go. Кроме того, он предоставляет
некоторые очень полезные функции для переноса и обработки ошибок.
Хорошим примером являются типизированные и объявленные ошибки
в предыдущем рецепте — они могут быть полезны для добавления
дополнительной информации к ошибке, но ее стандартное
обертывание изменит ее тип и нарушит утверждение типа:
// this wont work if you wrapped it
// in a standard way. that is,
// fmt.Errorf("custom error: %s", err.Error())
if err == Package.ErrorNamed{
//handle this error ina specific way
}
Этот рецепт продемонстрирует, как использовать пакет pkg/errors
для добавления аннотаций к ошибкам в вашем коде.

Как это сделать…
Эти шаги охватывают написание и запуск вашего приложения:
В терминале или консольном приложении создайте новый каталог
с именем ~/projects/go-programmingcookbook/chapter4/errwrap and Перейдите в этот каталог.
Выполните следующую команду:
$ go mod init github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter4/errwrap
Вы должны увидеть файл с именем go.mod, который содержит
следующее:
module github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter4/errwrap
Скопируйте тесты из ~/projects/go-programming-cookbookoriginal/chapter4/errwrap или используйте это как упражнение

Tlgm: @it_boooks

для написания собственного кода!
Создайте файл с именем errwrap.go со следующим содержимым:
package errwrap
import (
"fmt"
)

"github.com/pkg/errors"

// WrappedError demonstrates error wrapping and
// annotating an error
func WrappedError(e error) error {
return errors.Wrap(e, "An error occurred in
WrappedError")
}
// ErrorTyped is a error we can check against
type ErrorTyped struct{
error
}
// Wrap shows what happens when we wrap an error
func Wrap() {
e := errors.New("standard error")
fmt.Println("Regular Error - ",
WrappedError(e))

error")}))

fmt.Println("Typed Error - ",
WrappedError(ErrorTyped{errors.New("typed
fmt.Println("Nil -", WrappedError(nil))

}
Создайте файл unwrap.go со следующим содержимым:
package errwrap
import (

Tlgm: @it_boooks

"fmt"
)

"github.com/pkg/errors"

// Unwrap will unwrap an error and do
// type assertion to it
func Unwrap() {
err := error(ErrorTyped{errors.New("an error
occurred")})
err = errors.Wrap(err, "wrapped")
fmt.Println("wrapped error: ", err)
// we can handle many error types
switch errors.Cause(err).(type) {
case ErrorTyped:
fmt.Println("a typed error occurred: ",
err)

default:
fmt.Println("an unknown error occurred")
}
}

// StackTrace will print all the stack for
// the error
func StackTrace() {
err := error(ErrorTyped{errors.New("an error
occurred")})
err = errors.Wrap(err, "wrapped")
fmt.Printf("%+v\n", err)
}
Создайте новый каталог с именем example и перейдите к нему.
Создайте файл с именем main.go со следующим содержимым:
package main
import (
"fmt"

Tlgm: @it_boooks

)

"github.com/PacktPublishing/
Go-Programming-Cookbook-Second-Edition/
chapter4/errwrap"

func main() {
errwrap.Wrap()
fmt.Println()
errwrap.Unwrap()
fmt.Println()
errwrap.StackTrace()
}
Выполните go run main.go.
Вы также можете запустить следующие команды:
$ go build
$ ./example
Вы также можете запустить следующие команды:
$ go run main.go
Regular Error - An error occurred in WrappedError:
standard
error
Typed Error - An error occurred in WrappedError: typed
error
Nil -
wrapped error: wrapped: an error occurred
a typed error occurred: wrapped: an error occurred
an error occurred
github.com/PacktPublishing/Go-Programming-CookbookSecondEdition/chapter4/errwrap.StackTrace
/Users/lothamer/go/src/github.com/agtorre/gocookbook/chapter4/errwrap/unwrap.go:30
main.main
/tmp/go/src/github.com/agtorre/gocookbook/chapter4/errwrap/example/main.go:14
Файл go.mod должен быть обновлен, а файл go.sum теперь должен
присутствовать в каталоге рецептов верхнего уровня.

Tlgm: @it_boooks

Если вы скопировали или написали свои собственные тесты,
перейдите на один каталог вверх и запустите go test. Убедитесь,
что все тесты пройдены.

Как это работает…
Пакет pkg/errors — очень полезный инструмент. Имеет смысл
оборачивать каждую возвращенную ошибку с помощью этого пакета,
чтобы обеспечить дополнительный контекст при ведении журнала и
отладке ошибок. Это достаточно гибко, чтобы распечатать всю
трассировку стека при возникновении ошибки или просто добавить
префикс к вашим ошибкам при их печати. Он также может очищать
код, поскольку обернутый nil возвращает значение nil; например,
рассмотрим следующий код:
func RetError() error{
err := ThisReturnsAnError()
return errors.Wrap(err, "This only does something if err
!= nil")
}
В некоторых случаях это может избавить вас от необходимости
сначала проверять, является ли ошибка nil, прежде чем просто
вернуть ее. Этот рецепт демонстрирует, как использовать пакет для
переноса и распаковки ошибок, а также базовые функции трассировки
стека. Документация к пакету также предоставляет некоторые другие
полезные примеры, такие как печать частичных стеков. Дэйв Чейни,
автор этой библиотеки, также написал несколько полезных блогов и
выступил с докладами на эту тему; вы можете перейти на https://dave.c
heney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully,
чтобы узнать больше.

Использование пакета журнала и
понимание того, когда следует
регистрировать ошибки
Регистрация обычно должна происходить, когда конечным результатом
является ошибка. Другими словами, полезно вести журнал, когда

Tlgm: @it_boooks

происходит что-то исключительное или неожиданное. Также может
быть уместно, если вы используете журнал, который предоставляет
уровни журнала, для добавления операторов отладки или информации
в ключевые части вашего кода, чтобы быстро устранять проблемы во
время разработки. Слишком большое количество журналов затруднит
поиск чего-либо полезного, но недостаточное количество журналов
может привести к поломке системы без понимания основной причины.
Этот рецепт продемонстрирует использование стандартного пакета log
Go и некоторых полезных опций, а также продемонстрирует, когда,
вероятно, должен появиться журнал.

Как это сделать…
Эти шаги охватывают написание и запуск вашего приложения:
В терминале или консольном приложении создайте новый каталог
с именем ~/projects/go-programming-cookbook/chapter4/log
and Перейдите в этот каталог.
Выполните следующую команду:
$ go mod init github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter4/log
Вы должны увидеть файл с именем go.mod, который содержит
следующее:
module github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter4/log
Скопируйте тесты из ~/projects/go-programming-cookbookoriginal/chapter4/log или используйте это как упражнение для
написания собственного кода!
Создайте файл с именем log.go со следующим содержимым:
package log
import (
"bytes"
"fmt"
"log"
)

Tlgm: @it_boooks

// Log uses the setup logger
func Log() {
// we'll configure the logger to write
// to a bytes.Buffer
buf := bytes.Buffer{}
// second argument is the prefix last argument
is about

// options you combine them with a logical or.
logger := log.New(&buf, "logger: ",
log.Lshortfile|log.Ldate)
logger.Println("test")
logger.SetPrefix("new logger: ")

logger.Printf("you can also add args(%v) and
use Fatalln to
log and crash", true)
fmt.Println(buf.String())
}
Создайте файл с именем error.go со следующим содержимым:
package log
import "github.com/pkg/errors"
import "log"
// OriginalError returns the error original error
func OriginalError() error {
return errors.New("error occurred")
}
// PassThroughError calls OriginalError and
// forwards the error along after wrapping.
func PassThroughError() error {
err := OriginalError()
// no need to check error
// since this works with nil
return errors.Wrap(err, "in passthrougherror")
}

Tlgm: @it_boooks

// FinalDestination deals with the error
// and doesn't forward it
func FinalDestination() {
err := PassThroughError()
if err != nil {
// we log because an unexpected error
occurred!
log.Printf("an error occurred: %s\n",
err.Error())
return
}
}
Создайте новый каталог с именем example и перейдите к нему.
Создайте файл с именем main.go со следующим содержимым:
package main
import (
"fmt"

)

"github.com/PacktPublishing/
Go-Programming-Cookbook-Second-Edition/
chapter4/log"

func main() {
fmt.Println("basic logging and modification of
logger:")
log.Log()
fmt.Println("logging 'handled' errors:")
log.FinalDestination()
}
Выполните go run main.go.
Вы также можете запустить следующие команды:
$ go build
$ ./example
Вы также можете запустить следующие команды:

Tlgm: @it_boooks

$ go run main.go
basic logging and modification of logger:
logger: 2017/02/05 log.go:19: test
new logger: 2017/02/05 log.go:23: you can also add
args(true)
and use Fataln to log and crash
logging 'handled' errors:
2017/02/05 18:36:11 an error occurred: in
passthrougherror:
error occurred
Файл go.mod обновляется, и теперь файл go.sum должен
присутствовать в каталоге рецептов верхнего уровня.
Если вы скопировали или написали свои собственные тесты,
перейдите на один каталог вверх и запустите go test. Убедитесь,
что все тесты пройдены.

Как это работает…
Вы можете либо инициализировать регистратор и передавать его с
помощью log.NewLogger(), либо использовать регистратор уровня
пакета журнала для регистрации сообщений. Файл log.go в этом
рецепте делает первое, а error.go — второе. Он также показывает,
когда запись в журнал может иметь смысл после того, как ошибка
достигла конечного пункта назначения; в противном случае вполне
вероятно, что вы будете регистрироваться несколько раз для одного
события.
Есть несколько проблем с этим подходом. Во-первых, у вас может
быть дополнительный контекст в одной из промежуточных функций,
например, переменные, которые вы хотите регистрировать. Во-вторых,
регистрация множества переменных может привести к путанице,
сделать ее запутанной и трудной для чтения. В следующем рецепте
исследуется
структурированное
ведение
журналов,
которое
обеспечивает гибкость в регистрации переменных, а в следующем
рецепте мы также рассмотрим реализацию глобального регистратора
на уровне пакета.

Tlgm: @it_boooks

Структурированное ведение
журналов с помощью пакетов apex
и logrus
Основной причиной записи информации в журнал является проверка
состояния системы, когда события происходят или произошли в
прошлом. Основные сообщения журнала сложно прочесать, когда у
вас есть большое количество микросервисов, которые ведут журнал.
Существует множество сторонних пакетов для просмотра журналов,
если вы можете перевести журналы в формат данных, который они
понимают. Эти пакеты обеспечивают функции индексирования,
возможности поиска и многое другое. Пакеты sirupsen/logrus и
apex/log предоставляют способ ведения структурированного
журнала, в котором вы можете регистрировать ряд полей, которые
можно переформатировать, чтобы они соответствовали этим
сторонним программам чтения журналов. Например, можно просто
создавать журналы в формате JSON для анализа различными
службами.

Как это сделать…
Эти шаги охватывают написание и запуск вашего приложения:
В терминале или консольном приложении создайте новый каталог
с именем ~/projects/go-programmingcookbook/chapter4/structured and Перейдите в этот каталог.
Выполните следующую команду:
$ go mod init github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter4/structured
Вы должны увидеть файл с именем go.mod, который содержит
следующее:
module github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter4/structured
Скопируйте тесты из ~/projects/go-programming-cookbookoriginal/chapter4/structured или используйте это как

Tlgm: @it_boooks

упражнение для написания собственного кода!
Создайте файл logrus.go со следующим содержимым:
package structured
import "github.com/sirupsen/logrus"
// Hook will implement the logrus
// hook interface
type Hook struct {
id string
}
// Fire will trigger whenever you log
func (hook *Hook) Fire(entry *logrus.Entry) error
{
entry.Data["id"] = hook.id
return nil
}
// Levels is what levels this hook will fire on
func (hook *Hook) Levels() []logrus.Level {
return logrus.AllLevels
}
// Logrus demonstrates some basic logrus
functionality
func Logrus() {
// we're emitting in json format
logrus.SetFormatter(&logrus.TextFormatter{})
logrus.SetLevel(logrus.InfoLevel)
logrus.AddHook(&Hook{"123"})
fields := logrus.Fields{}
fields["success"] = true
fields["complex_struct"] = struct {
Event string
When string
}{"Something happened", "Just now"}
x := logrus.WithFields(fields)
x.Warn("warning!")

Tlgm: @it_boooks

x.Error("error!")
}
Создайте файл с именем apex.go со следующим содержимым:
package structured
import (
"errors"
"os"
"github.com/apex/log"
"github.com/apex/log/handlers/text"
)
// ThrowError throws an error that we'll trace
func ThrowError() error {
err := errors.New("a crazy failure")
log.WithField("id",
"123").Trace("ThrowError").Stop(&err)
return err
}
// CustomHandler splits to two streams
type CustomHandler struct {
id string
handler log.Handler
}
// HandleLog adds a hook and does the emitting
func (h *CustomHandler) HandleLog(e *log.Entry)
error {

e.WithField("id", h.id)
return h.handler.HandleLog(e)
}

// Apex has a number of useful tricks
func Apex() {
log.SetHandler(&CustomHandler{"123",
text.New(os.Stdout)})
err := ThrowError()
//With error convenience function

Tlgm: @it_boooks

log.WithError(err).Error("an error occurred")
}
Создайте новый каталог с именем example и перейдите к нему.
Создайте файл с именем main.go со следующим содержимым:
package main
import (
"fmt"
"github.com/PacktPublishing/
Go-Programming-Cookbook-Second-Edition/
chapter4/structured"
)
func main() {
fmt.Println("Logrus:")
structured.Logrus()
fmt.Println()
fmt.Println("Apex:")
structured.Apex()

}
Выполните go run main.go.
Вы также можете запустить следующие команды:
$ go build
$ ./example
Теперь вы должны увидеть следующий вывод:
$ go run main.go
Logrus:
WARN[0000] warning! complex_struct={Something happened
Just now}
id=123 success=true
ERRO[0000] error! complex_struct={Something happened Just
now}
id=123 success=true
Apex:
INFO[0000] ThrowError id=123

Tlgm: @it_boooks

ERROR[0000] ThrowError duration=133ns error=a crazy
failure
id=123
ERROR[0000] an error occurred error=a crazy failure
Файл go.mod должен быть обновлен, а файл go.sum теперь должен
присутствовать в каталоге рецептов верхнего уровня.
Если вы скопировали или написали свои собственные тесты,
перейдите на один каталог вверх и запустите go test. Убедитесь,
что все тесты пройдены.

Как это работает…
Пакеты sirupsen/logrus и apex/log являются отличными
структурированными регистраторами. Оба предоставляют хуки либо
для отправки нескольких событий, либо для добавления
дополнительных полей в запись журнала. Например, было бы
относительно просто использовать хук logrus или настраиваемый
обработчик apex для добавления номеров строк ко всем вашим
журналам, а также имен служб. Другое использование ловушки может
включать в себя traceID для отслеживания запроса в разных службах.
В то время как logrus разделяет хук и форматер, apex объединяет их.
В дополнение к этому в apex добавлены некоторые удобные функции,
такие как WithError для добавления поля error, а также трассировка,
обе из которых демонстрируются в этом рецепте. Также относительно
просто адаптировать хуки от logrus к обработчикам apex. Для обоих
решений было бы простым изменением преобразовать в
форматирование JSON вместо текста, окрашенного в ANSI.

Ведение журнала с пакетом
context
Этот рецепт продемонстрирует способ передачи полей журнала между
различными функциями. Пакет Go pkg/context — отличный способ
передачи дополнительных переменных и отмен между функциями.
Этот рецепт исследует использование этой функциональности для
распределения переменных между функциями для целей ведения
журнала.

Tlgm: @it_boooks

Этот стиль можно адаптировать к logrus или apex из предыдущего
рецепта. Мы будем использовать вершину для этого рецепта.

Как это сделать…
Эти шаги охватывают написание и запуск вашего приложения:
В терминале или консольном приложении создайте новый каталог
с именем ~/projects/go-programmingcookbook/chapter4/context and Перейдите в этот каталог.
Выполните следующую команду:
$ go mod init github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter4/context
Вы должны увидеть файл с именем go.mod, который содержит
следующее:
module github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter4/context
Скопируйте тесты из ~/projects/go-programming-cookbookoriginal/chapter4/context или используйте это как упражнение
для написания собственного кода!
Создайте файл с именем log.go со следующим содержимым:
package context
import (
"context"
)

"github.com/apex/log"

type key int
// logFields is a key we use
// for our context logging
const logFields key = 0
func getFields(ctx context.Context) *log.Fields {
fields, ok := ctx.Value(logFields).
(*log.Fields)

Tlgm: @it_boooks

if !ok {
f := make(log.Fields)
fields = &f
}
return fields
}
// FromContext takes an entry and a context
// then returns an entry populated from the
context object
func FromContext(ctx context.Context, l
log.Interface)
(context.Context, *log.Entry) {
fields := getFields(ctx)
e := l.WithFields(fields)
ctx = context.WithValue(ctx, logFields,
fields)
return ctx, e
}

value

// WithField adds a log field to the context
func WithField(ctx context.Context, key string,
interface{}) context.Context {
return WithFields(ctx, log.Fields{key:

value})
}
// WithFields adds many log fields to the context
func WithFields(ctx context.Context, fields
log.Fielder)
context.Context {
f := getFields(ctx)
for key, val := range fields.Fields() {
(*f)[key] = val
}
ctx = context.WithValue(ctx, logFields, f)
return ctx
}
Создайте файл с именем collect.go со следующим содержимым:

Tlgm: @it_boooks

package context
import (
"context"
"os"
"github.com/apex/log"
"github.com/apex/log/handlers/text"
)
// Initialize calls 3 functions to set up, then
// logs before terminating
func Initialize() {
// set basic log up
log.SetHandler(text.New(os.Stdout))
// initialize our context
ctx := context.Background()
// create a logger and link it to
// the context
ctx, e := FromContext(ctx, log.Log)
// set a field
ctx = WithField(ctx, "id", "123")
e.Info("starting")
gatherName(ctx)
e.Info("after gatherName")
gatherLocation(ctx)
e.Info("after gatherLocation")
}
func gatherName(ctx context.Context) {
ctx = WithField(ctx, "name", "Go Cookbook")
}
func gatherLocation(ctx context.Context) {
ctx = WithFields(ctx, log.Fields{"city":
"Seattle",
"state": "WA"})

}
Создайте новый каталог с именем example и перейдите к нему.
Создайте файл с именем main.go со следующим содержимым:

Tlgm: @it_boooks

package main
import "github.com/PacktPublishing/
Go-Programming-Cookbook-Second-Edition/
chapter4/context"
func main() {
context.Initialize()
}
Выполните go run main.go.
Вы также можете запустить следующие команды:
$ go build
$ ./example
Вы должны увидеть следующий вывод:
$ go run main.go
INFO[0000] starting id=123
INFO[0000] after gatherName id=123 name=Go Cookbook
INFO[0000] after gatherLocation city=Seattle id=123
name=Go
Cookbook state=WA
Файл go.mod обновляется, и теперь файл go.sum должен
присутствовать в каталоге рецептов верхнего уровня.
Если вы скопировали или написали свои собственные тесты,
перейдите на один каталог вверх и запустите go test. Убедитесь,
что все тесты пройдены.

Как это работает…
Пакет context теперь появляется в различных пакетах, включая
пакеты базы данных и HTTP. Этот рецепт позволит вам прикрепить
поля журнала к контексту и использовать их для целей ведения
журнала. Идея состоит в том, что отдельные методы могут
присоединять больше полей к контексту по мере его передачи, а затем
конечный сайт вызова может вести журнал и агрегировать
переменные.
Этот рецепт имитирует методы WithField и WithFields,
обнаруженные в пакетах протоколирования в предыдущем рецепте.

Tlgm: @it_boooks

Они изменяют одно значение, хранящееся в контексте, а также
обеспечивают другие преимущества использования контекста: отмену,
таймауты и безопасность потоков.

Использование глобальных
переменных уровня пакета
Пакеты apex и logrus в предыдущих примерах использовали
глобальную переменную уровня пакета. Иногда бывает полезно
структурировать ваши библиотеки так, чтобы они поддерживали обе
структуры с различными методами и функциями верхнего уровня,
чтобы вы могли использовать их напрямую, не передавая друг другу.
Этот рецепт также демонстрирует использование sync.Once, чтобы
гарантировать, что глобальное средство ведения журнала будет
инициализировано только один раз. Его также можно обойти с
помощью метода Set. Рецепт экспортирует только WithField и Debug,
но вы можете представить себе экспорт каждого метода,
прикрепленного к объекту log.

Как это сделать…
Эти шаги охватывают написание и запуск вашего приложения:
В терминале или консольном приложении создайте новый каталог
с именем ~/projects/go-programmingcookbook/chapter4/global and Перейдите в этот каталог.
Выполните следующую команду:
$ go mod init github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter4/global
Вы должны увидеть файл с именем go.mod, который содержит
следующее:
module github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter4/global
Скопируйте тесты из ~/projects/go-programming-cookbookoriginal/chapter4/global или используйте это как упражнение
для написания собственного кода!

Tlgm: @it_boooks

Создайте файл с именем global.go со следующим содержимым:
package global
import (
"errors"
"os"
"sync"
"github.com/sirupsen/logrus"
)
// we make our global package level
// variable lower case
var (
log *logrus.Logger
initLog sync.Once
)
// Init sets up the logger initially
// if run multiple times, it returns
// an error
func Init() error {
err := errors.New("already initialized")
initLog.Do(func() {
err = nil
log = logrus.New()
log.Formatter = &logrus.JSONFormatter{}
log.Out = os.Stdout
log.Level = logrus.DebugLevel
})
return err
}
// SetLog sets the log
func SetLog(l *logrus.Logger) {
log = l
}
// WithField exports the logs withfield connected
// to our global log
func WithField(key string, value interface{})
*logrus.Entry {

Tlgm: @it_boooks

return log.WithField(key, value)
}
// Debug exports the logs Debug connected
// to our global log
func Debug(args ...interface{}) {
log.Debug(args...)
}
Создайте файл с именем log.go со следующим содержимым:
package global
// UseLog demonstrates using our global
// log
func UseLog() error {
if err := Init(); err != nil {
return err
}
// if we were in another package these would be
// global.WithField and
// global.Debug
WithField("key", "value").Debug("hello")
Debug("test")
return nil
}
Создайте новый каталог с именем example и перейдите к нему.
Создайте файл с именем main.go со следующим содержимым:
package main
import "github.com/PacktPublishing/
Go-Programming-Cookbook-Second-Edition/
chapter4/global"
func main() {
if err := global.UseLog(); err != nil {
panic(err)
}
}

Tlgm: @it_boooks

Выполните go run main.go.
Вы также можете запустить следующие команды:
$ go build
$ ./example
Теперь вы должны увидеть следующий вывод:
$ go run main.go
{"key":"value","level":"debug","msg":"hello","time":"2017
-0212T19:22:50-08:00"}
{"level":"debug","msg":"test","time":"2017-0212T19:22:5008:00"}
Файл go.mod обновляется, и теперь файл go.sum должен
присутствовать в каталоге рецептов верхнего уровня.
Если вы скопировали или написали свои собственные тесты,
перейдите на один каталог вверх и запустите go test. Убедитесь,
что все тесты пройдены.

Как это работает…
Обычный шаблон для этих global объектов уровня пакета
заключается в том, чтобы не экспортировать global переменную и
предоставлять только желаемую функциональность через методы. Как
правило, вы также можете включить метод для возврата копии global
средства ведения журнала для пакетов, которым требуется объект
средства ведения журнала.
Тип sync.Once — это недавно введенная структура. Эта структура в
сочетании с методом Do будет выполняться в коде только один раз. Мы
используем это в нашем коде инициализации, и функция Init выдаст
ошибку, если Init вызывается более одного раза. Мы используем
пользовательскую функцию Init вместо встроенной функции init(),
если мы хотим передать параметры в наш global журнал.
Хотя в этом примере используется журнал, вы также можете
представить случаи, когда это может быть полезно с подключением к
базе данных, потоками данных и рядом других вариантов
использования.

Tlgm: @it_boooks

Отлов паники для долго
выполняющихся процессов
При реализации длительных процессов некоторые пути кода могут
привести к панике. Обычно это характерно для таких вещей, как
неинициализированные карты и указатели, а также проблемы с
делением на ноль в случае плохо проверенного пользовательского
ввода.
Полный сбой программы в таких случаях часто намного хуже, чем
сама паника, поэтому может быть полезно поймать и справиться с
паникой.

Как это сделать…
Эти шаги охватывают написание и запуск вашего приложения:
В терминале или консольном приложении создайте новый каталог
с именем ~/projects/go-programmingcookbook/chapter4/panic and Перейдите в этот каталог.
Выполните следующую команду:
$ go mod init github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter4/panic
Вы должны увидеть файл с именем go.mod, который содержит
следующее:
module github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter4/panic
Скопируйте тесты из ~/projects/go-programming-cookbookoriginal/chapter4/panic или используйте это как упражнение
для написания собственного кода!
Создайте файл с именем panic.go со следующим содержимым:
package panic
import (
"fmt"
"strconv"

Tlgm: @it_boooks

)
// Panic panics with a divide by zero
func Panic() {
zero, err := strconv.ParseInt("0", 10, 64)
if err != nil {
panic(err)
}

}

a := 1 / zero
fmt.Println("we'll never get here", a)

// Catcher calls Panic
func Catcher() {
defer func() {
if r := recover(); r != nil {
fmt.Println("panic occurred:", r)
}
}()
Panic()
}
Создайте новый каталог с именем example и перейдите к нему.
Создайте файл с именем main.go со следующим содержимым:
package main
import (
"fmt"
"github.com/PacktPublishing/
Go-Programming-Cookbook-Second-Edition/
chapter4/panic"
)
func main() {
fmt.Println("before panic")
panic.Catcher()
fmt.Println("after panic")
}
Выполните go run main.go.
Вы также можете запустить следующие команды:

Tlgm: @it_boooks

$ go build
$ ./example
Вы должны увидеть следующий вывод:
$ go run main.go
before panic
panic occurred: runtime error: integer divide by zero
after panic
Если вы скопировали или написали свои собственные тесты,
перейдите на один каталог вверх и запустите go test. Убедитесь,
что все тесты пройдены.

Как это работает…
Этот рецепт является очень простым примером того, как поймать
панику. С помощью более сложного промежуточного программного
обеспечения вы можете представить, как можно отложить
восстановление и перехватить его после выполнения множества
вложенных функций. Во время восстановления вы можете, по сути,
делать все, что хотите, хотя создание журнала является обычным
явлением.
В большинстве веб-приложений принято перехватывать паники и
выдавать сообщение http.InternalServerError при возникновении
паники.

Tlgm: @it_boooks

5. Сетевое
программирование
Стандартная библиотека Go обеспечивает большую поддержку сетевых
операций. Он включает в себя пакеты, позволяющие управлять TCP/IP,
UDP, DNS, почтой и RPC с помощью HTTP. Сторонние пакеты также
могут заполнить пробелы в стандартной библиотеке, включая
gorilla/websockets
(https://github.com/gorilla/websocket/)
для
реализации WebSocket, которую можно использовать в обычном
обработчике HTTP. В этой главе рассматриваются эти библиотеки и
демонстрируются некоторые простые рецепты использования каждой
из них. Эти рецепты помогут разработчикам, которые не могут
использовать абстракции более высокого уровня, такие как REST или
GRPC, но нуждаются в сетевом подключении. Это также полезно для
приложений DevOps, которым необходимо выполнять поиск DNS или
работать с необработанными электронными письмами. Прочитав эту
главу, вы должны были немного освоить базовое сетевое
программирование и быть готовым к более глубокому погружению.
В этой главе будут рассмотрены следующие рецепты:
Написание эхо-сервера и клиента TCP/IP
Написание UDP-сервера и клиента
Работа с разрешением доменного имени
Работа с веб-сокетами
Работа с net/rpc для вызова удаленных методов
Использование net/mail для разбора писем

Технические требования
Чтобы продолжить выполнение всех рецептов в этой главе, настройте
свою среду в соответствии со следующими шагами:
Загрузите и установите Go 1.12.6 или более позднюю версию в
своей операционной системе по адресу
https://golang.org/doc/install.

Tlgm: @it_boooks

Откройте терминал или консольное приложение, создайте и
перейдите в каталог проекта, например ~/projects/goprogramming-cookbook. Весь наш код будет запускаться и
изменяться из этого каталога.
Скопируйте последний код в ~/projects/go-programmingcookbook-original и, при желании, работайте из этого каталога,
вместо того, чтобы вводить примеры вручную:
$ git clone git@github.com:PacktPublishing/GoProgramming-Cookbook-Second-Edition.git go-programmingcookbook-original

Написание эхо-сервера и клиента
TCP/IP
TCP/IP — это распространенный сетевой протокол, а протокол HTTP
был построен на его основе. TCP требует, чтобы клиент подключался к
серверу для отправки и получения данных. Этот рецепт будет
использовать пакет net для создания TCP-соединения между клиентом
и сервером. Клиент отправит пользовательский ввод на сервер, и
сервер ответит той же введенной строкой, но преобразованной в
верхний регистр с использованием результатов strings.ToUpper().
Клиент будет печатать любые сообщения, полученные от сервера,
поэтому он должен выводить версию нашего ввода в верхнем регистре.

Как это сделать…
Эти шаги охватывают написание и запуск вашего приложения:
From your Terminal or console application, create a new directory
called ~/projects/go-programming-cookbook/chapter5/tcp and
Перейдите в этот каталог.
Выполните следующую команду:
$ go mod init github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter5/tcp
Вы должны увидеть файл с именем go.mod, который содержит
следующее:

Tlgm: @it_boooks

module github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter5/tcp
Скопируйте тесты из ~/projects/go-programming-cookbookoriginal/chapter5/tcp или используйте это как упражнение для
написания собственного кода!
Create a new directory named server and navigate to it.
Создайте файл с именем main.go со следующим содержимым:
package main
import (
"bufio"
"fmt"
"net"
"strings"
)
const addr = "localhost:8888"
func echoBackCapitalized(conn net.Conn) {
// set up a reader on conn (an io.Reader)
reader := bufio.NewReader(conn)

}

// grab the first line of data encountered
data, err := reader.ReadString('\n')
if err != nil {
fmt.Printf("error reading data: %s\n", err.Error())
return
}
// print then send back the data
fmt.Printf("Received: %s", data)
conn.Write([]byte(strings.ToUpper(data)))
// close up the finished connection
conn.Close()

func main() {
ln, err := net.Listen("tcp", addr)
if err != nil {
panic(err)
}
defer ln.Close()

Tlgm: @it_boooks

fmt.Printf("listening on: %s\n", addr)
for {
conn, err := ln.Accept()
if err != nil {
fmt.Printf("encountered an error accepting
connection: %s\n",
err.Error())
// if there's an error try again
continue
}
// handle this asynchronously
// potentially a good use-case
// for a worker pool
go echoBackCapitalized(conn)
}
}
Перейдите к предыдущему каталогу.
Создайте новый каталог с именем client и перейдите в него.
Создайте файл с именем main.go со следующим содержимым:
package main
import (
"bufio"
"fmt"
"net"
"os"
)
const addr = "localhost:8888"
func main() {
reader := bufio.NewReader(os.Stdin)
for {
// grab a string input from the clie
fmt.Printf("Enter some text: ")
data, err := reader.ReadString('\n')
if err != nil {
fmt.Printf("encountered an error reading input:
%s\n",
err.Error())
continue

Tlgm: @it_boooks

}
// connect to the addr
conn, err := net.Dial("tcp", addr)
if err != nil {
fmt.Printf("encountered an error connecting: %s\n",
err.Error())
}
// write the data to the connection
fmt.Fprintf(conn, data)
// read back the response
status, err := bufio.NewReader(conn).ReadString('\n')
if err != nil {
fmt.Printf("encountered an error reading response:
%s\n",
err.Error())
}
fmt.Printf("Received back: %s", status)
// close up the finished connection
conn.Close()
}
}
Перейдите к предыдущему каталогу.
Запустите go run ./server и вы увидите следующий вывод:
$ go run ./server
listening on: localhost:8888
В отдельном терминале запустите go run ./client из каталога
tcp, и вы увидите следующий вывод:
$ go run ./client
Enter some text:
Введите this is a test и нажмите Enter. Вы увидите
следующее:
$ go run ./client
Enter some text: this is a test
Received back: THIS IS A TEST
Enter some text:
Нажмите Ctrl + C, чтобы выйти.

Tlgm: @it_boooks

Если вы скопировали или написали свои собственные тесты,
перейдите на один каталог вверх и запустите go test. Убедитесь,
что все тесты пройдены.

Как это работает…
Сервер прослушивает порт 8888. Всякий раз, когда приходит запрос,
сервер должен принять запрос и управлять клиентским соединением. В
случае с этой программой она отправляет горутину, которая читает
запрос от клиента, использует полученные данные с заглавной буквы,
отправляет их обратно клиенту и, наконец, закрывает соединение.
Сервер сразу же снова зацикливается, ожидая получения новых
клиентских подключений, в то время как предыдущее подключение
обрабатывается отдельно.
Клиент считывает ввод из STDIN, подключается к адресу через
соединение TCP, записывает сообщение, которое было прочитано из
ввода, а затем распечатывает ответ от сервера. После этого он
закрывает соединение и снова зацикливает чтение из STDIN. Вы также
можете переработать этот пример, чтобы клиент оставался
подключенным до выхода из программы, а не при каждом запросе.

Написание UDP-сервера и клиента
Протокол UDP часто используется для игр и в местах, где скорость
важнее надежности. UDP-серверы и клиенты не должны соединяться
друг с другом. Этот рецепт создаст UDP-сервер, который будет
прослушивать сообщения от клиентов, добавлять их IP-адреса в свой
список и рассылать сообщения каждому из ранее замеченных
клиентов.
Сервер будет писать сообщение в STDOUT всякий раз, когда клиент
подключается, и он будет транслировать одно и то же сообщение всем
своим клиентам. Текст этого сообщения должен быть Sent ,
где будет увеличиваться каждый раз, когда сервер выполняет
широковещательную рассылку всем своим клиентам. В результате
count может иметь разные значения в зависимости от того, сколько
времени вам потребуется для подключения к вашему клиенту,

Tlgm: @it_boooks

поскольку сервер будет транслировать сообщения независимо от
количества клиентов, которым он отправляет сообщение.

Как это сделать…
Эти шаги охватывают процесс написания и запуска вашего
приложения:
В терминале или консольном приложении создайте новый каталог
с именем ~/projects/go-programming-cookbook/chapter5/udp и
перейдите в этот каталог.
Выполните следующую команду:
$ go mod init github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter5/udp
Вы должны увидеть файл с именем go.mod, который содержит
следующее:
module github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter5/udp
Скопируйте тесты из ~/projects/go-programming-cookbookoriginal/chapter5/udp или используйте это как упражнение для
написания собственного кода!
Создайте новый каталог с именем server и перейдите к нему.
Создайте файл с именем broadcast.go со следующим
содержимым:
package main
import (
"fmt"
"net"
"sync"
"time"
)
type connections struct {
addrs map[string]*net.UDPAddr
// lock for modifying the map
mu sync.Mutex
}

Tlgm: @it_boooks

func broadcast(conn *net.UDPConn, conns *connections) {
count := 0
for {
count++
conns.mu.Lock()
// loop over known addresses
for _, retAddr := range conns.addrs {
// send a message to them all
msg := fmt.Sprintf("Sent %d", count)
if _, err := conn.WriteToUDP([]byte(msg), retAddr);
err != nil {
fmt.Printf("error encountered: %s", err.Error())
continue
}
}
conns.mu.Unlock()
time.Sleep(1 * time.Second)
}

}
Создайте файл с именем main.go со следующим содержимым:
package main
import (
"fmt"
"net"
)
const addr = "localhost:8888"
func main() {
conns := &connections{
addrs: make(map[string]*net.UDPAddr),
}
fmt.Printf("serving on %s\n", addr)
// construct a udp addr
addr, err := net.ResolveUDPAddr("udp", addr)

Tlgm: @it_boooks

if err != nil {
panic(err)
}
// listen on our specified addr
conn, err := net.ListenUDP("udp", addr)
if err != nil {
panic(err)
}
// cleanup
defer conn.Close()
// async send messages to all known clients
go broadcast(conn, conns)
msg := make([]byte, 1024)
for {
// receive a message to gather the ip address
// and port to send back to
_, retAddr, err := conn.ReadFromUDP(msg)
if err != nil {
continue
}

}

//store it in a map
conns.mu.Lock()
conns.addrs[retAddr.String()] = retAddr
conns.mu.Unlock()
fmt.Printf("%s connected\n", retAddr)

}
Перейдите к предыдущему каталогу.
Создайте новый каталог с именем client и перейдите в него.
Создайте файл с именем main.go со следующим содержимым:
package main
import (
"fmt"
"net"
)

Tlgm: @it_boooks

const addr = "localhost:8888"
func main() {
fmt.Printf("client for server url: %s\n", addr)
addr, err := net.ResolveUDPAddr("udp", addr)
if err != nil {
panic(err)
}
conn, err := net.DialUDP("udp", nil, addr)
if err != nil {
panic(err)
}
defer conn.Close()
msg := make([]byte, 512)
n, err := conn.Write([]byte("connected"))
if err != nil {
panic(err)
}
for {
n, err = conn.Read(msg)
if err != nil {
continue
}
fmt.Printf("%s\n", string(msg[:n]))
}

}
Перейдите к предыдущему каталогу.
Запустите go run ./server и вы увидите следующий вывод:
$ go run ./server
serving on localhost:8888
В отдельном терминале запустите go run ./client из каталога
udp, и вы увидите следующий вывод, хотя количество может
отличаться:
$ go run ./client
client for server url: localhost:8888
Sent 3

Tlgm: @it_boooks

Sent 4
Sent 5
Перейдите к терминалу, на котором работает сервер, и вы должны
увидеть что-то похожее на следующее:
$ go run ./server
serving on localhost:8888
127.0.0.1:64242 connected
Нажмите Ctrl + C, чтобы выйти из сервера и клиента.
Если вы скопировали или написали свои собственные тесты,
перейдите на один каталог вверх и запустите go test. Убедитесь,
что все тесты пройдены.

Как это работает…
Сервер прослушивает порт 8888, как и в предыдущем рецепте. Если
клиент запускается, он отправляет сообщение на сервер, и сервер
добавляет свой адрес в список адресов. Поскольку клиенты могут
подключаться асинхронно, сервер должен использовать мьютекс перед
изменением или чтением из списка.
Отдельная широковещательная горутина запускается отдельно и
отправляет одно и то же сообщение на все адреса клиентов, которые
ранее отправляли ей сообщения. Предполагая, что они все еще
слушают, они получат одно и то же сообщение с сервера примерно в
одно и то же время. Вы также можете подключиться к большему
количеству клиентов, чтобы увидеть, как это работает.

Работа с разрешением доменного
имени
Пакет net предоставляет ряд полезных функций для поиска DNS. Эта
информация сравнима с той, которую вы можете получить, используя
команду dig в Unix. Эта информация может оказаться чрезвычайно
полезной для реализации любого вида сетевого программирования,
требующего динамического определения IP-адресов.
Этот рецепт исследует, как вы можете собрать эти данные. Чтобы
продемонстрировать это, мы реализуем упрощенную команду dig. Мы

Tlgm: @it_boooks

постараемся сопоставить URL-адрес со всеми его адресами IPv4 и
IPv6. Изменив GODEBUG=netdns= на go или cgo, он будет использовать
либо чистый преобразователь Go DNS, либо преобразователь cgo. По
умолчанию используется чистый DNS-преобразователь Go.

Как это сделать…
Эти шаги охватывают написание и запуск вашего приложения:
В терминале или консольном приложении создайте новый каталог
с именем ~/projects/go-programming-cookbook/chapter5/dns и
перейдите в этот каталог.
Выполните следующую команду:
$ go mod init github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter5/dns
Вы должны увидеть файл с именем go.mod, который содержит
следующее:
module github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter5/dns
Скопируйте тесты из ~/projects/go-programming-cookbookoriginal/chapter5/dns или используйте это как упражнение для
написания собственного кода!
Создайте файл dns.go со следующим содержимым:
package dns
import (
"fmt"
"net"
)

"github.com/pkg/errors"

// Lookup holds the DNS information we care about
type Lookup struct {
cname string
hosts []string
}

Tlgm: @it_boooks

// We can use this to print the lookup object
func (d *Lookup) String() string {
result := ""
for _, host := range d.hosts {
result += fmt.Sprintf("%s IN A %s\n", d.cname, host)
}
return result
}
// LookupAddress returns a DNSLookup consisting of a
cname and host
// for a given address
func LookupAddress(address string) (*Lookup, error) {
cname, err := net.LookupCNAME(address)
if err != nil {
return nil, errors.Wrap(err, "error looking up
CNAME")
}
hosts, err := net.LookupHost(address)
if err != nil {
return nil, errors.Wrap(err, "error looking up HOST")
}
return &Lookup{cname: cname, hosts: hosts}, nil
}
Создайте новый каталог с именем example и перейдите к нему.
Создайте файл main.go со следующим содержимым:
package main
import (
"fmt"
"log"
"os"
"github.com/PacktPublishing/Go-Programming-CookbookSecond-Edition/chapter5/dns"
)
func main() {
if len(os.Args) < 2 {
fmt.Printf("Usage: %s \n", os.Args[0])

Tlgm: @it_boooks

os.Exit(1)
}
address := os.Args[1]
lookup, err := dns.LookupAddress(address)
if err != nil {
log.Panicf("failed to lookup: %s", err.Error())
}
fmt.Println(lookup)

}
Запустите команду go run main.go golang.org.
Вы также можете запустить следующее:
$ go build
$ ./example golang.org
Вы должны увидеть следующий вывод:

$ go run main.go golang.org
golang.org. IN A 172.217.5.17
golang.org. IN A 2607:f8b0:4009:809::2011
Файл go.mod может быть обновлен, и теперь файл go.sum должен
присутствовать в каталоге рецептов верхнего уровня.
Если вы скопировали или написали свои собственные тесты,
перейдите на один каталог вверх и запустите go test. Убедитесь,
что все тесты пройдены.

Как это работает…
Этот рецепт выполнил CNAME и поиск хоста по предоставленному
адресу. В нашем случае мы использовали golang.org. Мы сохраняем
результат в структуре поиска, которая выводит результаты вывода с
помощью метода String(). Этот метод будет вызываться
автоматически, когда мы печатаем наш объект в виде строки, или мы
можем вызвать метод напрямую. Мы реализуем некоторую базовую
проверку аргументов в main.go, чтобы убедиться, что адрес
предоставляется при запуске программы.

Работа с веб-сокетами

Tlgm: @it_boooks

WebSockets позволяют серверному приложению подключаться к вебклиенту, написанному на JavaScript. Это позволяет создавать вебприложения с двусторонней связью и создавать обновления, такие как
чаты и многое другое.
В этом рецепте рассматривается написание сервера WebSocket на Go, а
также демонстрируется процесс использования клиентом и
взаимодействия
с
сервером
WebSocket.
Он
использует
github.com/gorilla/websocket
для
обновления
стандартного
обработчика до обработчика WebSocket, а также для создания
клиентского приложения.

Как это сделать…
Эти шаги охватывают написание и запуск вашего приложения:
В терминале или консольном приложении создайте новый каталог
с именем ~/projects/go-programmingcookbook/chapter5/websocket и перейдите в этот каталог.
Выполните следующую команду:
$ go mod init github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter5/websocket
Вы должны увидеть файл с именем go.mod, который содержит
следующее:
module github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter5/websocket
Скопируйте тесты из ~/projects/go-programming-cookbookoriginal/chapter5/websocket или используйте это как
упражнение для написания собственного кода!
Создайте новый каталог с именем server и перейдите к нему.
Создайте файл с именем handler.go со следующим содержимым:
package main
import (
"log"
"net/http"
"github.com/gorilla/websocket"

Tlgm: @it_boooks

)
// upgrader takes an http connection and converts it
// to a websocket one, we're using some recommended
// basic buffer sizes
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
}
func wsHandler(w http.ResponseWriter, r *http.Request) {
// upgrade the connection
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println("failed to upgrade connection: ", err)
return
}
for {
// read and echo back messages in a loop
messageType, p, err := conn.ReadMessage()
if err != nil {
log.Println("failed to read message: ", err)
return
}
log.Printf("received from client: %#v", string(p))
if err := conn.WriteMessage(messageType, p); err !=
nil {
log.Println("failed to write message: ", err)
return
}
}
}
Создайте файл с именем main.go со следующим содержимым:
package main
import (
"fmt"
"log"
"net/http"
)

Tlgm: @it_boooks

func main() {
fmt.Println("Listening on port :8000")
// we mount our single handler on port localhost:8000 to
handle all
// requests
log.Panic(http.ListenAndServe("localhost:8000",
http.HandlerFunc(wsHandler)))
}
Перейдите к предыдущему каталогу.
Создайте новый каталог с именем client и перейдите в него.
Создайте файл с именем process.go со следующим содержимым:
package main
import (
"bufio"
"fmt"
"log"
"os"
"strings"
"github.com/gorilla/websocket"
)
func process(c *websocket.Conn) {
reader := bufio.NewReader(os.Stdin)
for {
fmt.Printf("Enter some text: ")
// this will block ctrl-c, to exit press it then hit
enter
// or kill from another location
data, err := reader.ReadString('\n')
if err != nil {
log.Println("failed to read stdin", err)
}
// trim off the space from reading the string
data = strings.TrimSpace(data)
// write the message as a byte across the websocket
err = c.WriteMessage(websocket.TextMessage,
[]byte(data))

Tlgm: @it_boooks

if err != nil {
log.Println("failed to write message:", err)
return
}
// this is an echo server, so we can always read
after the write
_, message, err := c.ReadMessage()
if err != nil {
log.Println("failed to read:", err)
return
}
log.Printf("received back from server: %#v\n",
string(message))
}
}
Создайте файл с именем main.go со следующим содержимым:
package main
import (
"log"
"os"
"os/signal"
"github.com/gorilla/websocket"
)
// catchSig cleans up our websocket conenction if we kill
the program
// with a ctrl-c
func catchSig(ch chan os.Signal, c *websocket.Conn) {
// block on waiting for a signal

// as out
func (w *Worker) Encode(ctx context.Context) {
for {
select {
case TWVzc2FnZTk=
Message5 => TWVzc2FnZTU=
Message11 => TWVzc2FnZTEx
Message10 => TWVzc2FnZTEw
Message4 => TWVzc2FnZTQ=
Message12 => TWVzc2FnZTEy
Message6 => TWVzc2FnZTY=
Message14 => TWVzc2FnZTE0
Message13 => TWVzc2FnZTEz
Message0 => TWVzc2FnZTA=
Message15 => TWVzc2FnZTE1
Message1 => TWVzc2FnZTE=
Message17 => TWVzc2FnZTE3
Message16 => TWVzc2FnZTE2
Message19 => TWVzc2FnZTE5
Message18 => TWVzc2FnZTE4
Message2 => TWVzc2FnZTI=
Файл go.mod может быть обновлен, и теперь файл go.sum должен
присутствовать в каталоге рецептов верхнего уровня.
Если вы скопировали или написали свои собственные тесты,
перейдите на один каталог вверх и запустите go test. Убедитесь,

Tlgm: @it_boooks

что все тесты пройдены.

Как это работает…
Пакет main создает конвейер, состоящий из 10 энкодеров и 2
принтеров. Он ставит в очередь 20 строк на входном канале и ожидает
20 ответов на выходном канале. Если сообщения достигают выходного
канала, это означает, что они успешно прошли весь конвейер.
Функция NewPipeline используется для подключения пулов. Это
гарантирует, что каналы создаются с правильно буферизованными
размерами и что выходные каналы некоторых пулов подключены к
соответствующим входным каналам других пулов. Также возможно
разветвить конвейер, используя массив входных каналов и массив
выходных каналов для каждого рабочего процесса, несколько
именованных каналов или карты каналов. Это позволило бы на каждом
этапе отправлять сообщения в регистратор.

Tlgm: @it_boooks

11. Распределенные системы
Иногда параллелизма на уровне приложения недостаточно, и вещи,
кажущиеся простыми в разработке, могут усложниться во время
развертывания. Распределенные системы создают ряд проблем, которых
нет при разработке на одной машине. Эти приложения усложнили такие
вещи, как мониторинг, написание приложений, требующих строгих
гарантий согласованности, и обнаружение сервисов. Кроме того, вы
всегда должны помнить об единых точках отказа, таких как база
данных, иначе ваши распределенные приложения могутвыйти из строя,
когда этот единственный компонент выйдет из строя.
В этой главе будут рассмотрены методы управления распределенными
данными, оркестровки, контейнеризации, метрик и мониторинга. Они
станут частью вашего набора инструментов для написания и поддержки
микросервисов и больших распределенных приложений.
В этой главе мы рассмотрим следующие рецепты:
Использование службы обнаружения с Consul
Реализация базового консенсуса с использованием Raft
Использование контейнеризации с Docker
Стратегии оркестрации и развертывания
Приложения для мониторинга
Сбор метрик

Технические требования
Чтобы следовать всем рецептам этой главы, настройте свою среду в
соответствии со следующими шагами:
Загрузите и установите Go 1.12.6 или более позднюю версию в
своей операционной системе по адресу https://golang.org/doc/install.
Install Consul from https://www.consul.io/intro/getting-started/install.ht
ml.
Откройте терминал или консольное приложение, создайте и
перейдите в каталог проекта, например ~/projects/goprogramming-cookbook. Весь наш код будет запускаться и
изменяться из этого каталога.

Tlgm: @it_boooks

Скопируйте последний код в ~/projects/go-programmingcookbook-original и (необязательно) работайте из этого каталога,
а не вводите примеры вручную:
$ git clone git@github.com:PacktPublishing/GoProgramming-Cookbook-Second-Edition.git go-programmingcookbook-original

Использование службы
обнаружения с Consul
При использовании микросервисного подхода к приложениям вы
получаете множество серверов, прослушивающих различные IP-адреса,
домены и порты. Эти IP-адреса будут различаться в зависимости от
среды (промежуточная или производственная), и может быть сложно
сохранить их статическими для настройки между службами. Вы также
хотите знать, когда компьютер или служба отключены или недоступны
из-за сетевого раздела. Сетевой раздел возникает, когда две части сети
не могут связаться друг с другом. Например, если происходит сбой
коммутатора между двумя центрами обработки данных, службы в
одном центре обработки данных не могут получить доступ к службам в
другом центре обработки данных. Consul — это инструмент,
предоставляющий множество функций, но здесь мы рассмотрим
регистрацию сервисов в Consul и запрос к ним из других наших
сервисов.

Как это сделать…
Эти шаги охватывают написание и запуск вашего приложения:
В терминале или консольном приложении создайте новый каталог
с именем ~/projects/go-programmingcookbook/chapter11/discovery и перейдите к нему.
Выполните следующую команду:
$ go mod init github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter11/discovery
Вы должны увидеть файл с именем go.mod, который содержит
следующее:

Tlgm: @it_boooks

module github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter11/discovery
Скопируйте тесты из ~/projects/go-programming-cookbookoriginal/chapter11/discovery или используйте это как
возможность написать свой собственный код!
Создайте файл client.go со следующим содержимым:
package discovery
import "github.com/hashicorp/consul/api"
// Client exposes api methods we care
// about
type Client interface {
Register(tags []string) error
Service(service, tag string)
([]*api.ServiceEntry,
*api.QueryMeta, error)
}
type client struct {
client *api.Client
address string
name string
port int
}
//NewClient iniitalizes a consul client
func NewClient(config *api.Config, address, name
string, port
int) (Client, error) {
c, err := api.NewClient(config)
if err != nil {
return nil, err
}
cli := &client{
client: c,
name: name,
address: address,
port: port,
}

Tlgm: @it_boooks

return cli, nil
}
Создайте файл с именем operations.go со следующим
содержимым:
package discovery
import "github.com/hashicorp/consul/api"
// Register adds our service to consul
func (c *client) Register(tags []string) error {
reg := &api.AgentServiceRegistration{
ID: c.name,
Name: c.name,
Port: c.port,
Address: c.address,
Tags: tags,
}
return c.client.Agent().ServiceRegister(reg)
}
// Service return a service
func (c *client) Service(service, tag string)
([]*api.ServiceEntry, *api.QueryMeta, error) {
return c.client.Health().Service(service, tag,
false,

nil)

}
Создайте файл с именем exec.go со следующим содержимым:
package discovery
import "fmt"
// Exec creates a consul entry then queries it
func Exec(cli Client) error {
if err := cli.Register([]string{"Go", "Awesome"}); err
!= nil {
return err
}
entries, _, err := cli.Service("discovery", "Go")

Tlgm: @it_boooks

if err != nil {
return err
}
for _, entry := range entries {
fmt.Printf("%#v\n", entry.Service)
}
return nil
}
Создайте новый каталог с именем example и перейдите к нему.
Создайте файл с именем main.go со следующим содержимым:
package main
import "github.com/PacktPublishing/
Go-Programming-Cookbook-Second-Edition/
chapter11/discovery"
func main() {
if err := discovery.Exec(); err != nil {
panic(err)
}
}
Запустите Consul в отдельном Терминале с помощью команды
consul agent -dev -node=localhost.
Запустите команду go run main.go.
Вы также можете запустить следующие команды:
$ go build
$ ./example
Вы должны увидеть следующий вывод:
$ go run main.go
&api.AgentService{ID:"discovery", Service:"discovery",
Tags:
[]string{"Go", "Awesome"}, Port:8080,
Address:"localhost",
EnableTagOverride:false, CreateIndex:0x23,
ModifyIndex:0x23}
Файл go.mod может быть обновлен, и теперь файл go.sum должен
присутствовать в каталоге рецептов верхнего уровня.

Tlgm: @it_boooks

Если вы скопировали или написали свои собственные тесты,
перейдите на один каталог вверх и запустите go test. Убедитесь,
что все тесты пройдены.

Как это работает…
Consul предоставляет надежную библиотеку Go API. Это может
показаться пугающим, когда вы начинаете в первый раз, но этот рецепт
показывает, как вы можете подойти к его обертыванию. Дальнейшая
настройка Consul выходит за рамки этого рецепта; здесь показаны
основы регистрации службы и запроса других служб при наличии
ключа и тега.
Это можно было бы использовать для регистрации новых микрослужб
во время запуска, запроса всех зависимых служб и отмены регистрации
при завершении работы. Вы также можете кэшировать эту
информацию, чтобы не нажимать Consul для каждого запроса, но этот
рецепт предоставляет основные инструменты, которые вы можете
расширить. Агент Consul также делает эти повторные запросы
быстрыми и эффективными (https://www.consul.io/intro/getting-started/ag
ent.html).

Реализация базового консенсуса с
использованием Raft
Raft — алгоритм консенсуса. Это позволяет распределенным системам
сохранять общее и управляемое состояние (https://raft.github.io/).
Настройка системы Raft сложна во многих отношениях — во-первых,
вам нужен консенсус, чтобы выборы состоялись и прошли успешно.
Это может быть сложно запустить, когда вы работаете с несколькими
узлами, и вам может быть трудно начать работу. Базовый кластер можно
запустить на одном узле/лидере. Однако, если вам нужна избыточность,
необходимо как минимум три узла, чтобы предотвратить потерю
данных в случае отказа одного узла. Эта концепция известна как
кворум, при которой вы должны поддерживать (n/2)+1 доступных
узлов, чтобы обеспечить возможность фиксации новых журналов в
кластере Raft. По сути, если вы можете поддерживать кворум, кластер
остается работоспособным и пригодным для использования.

Tlgm: @it_boooks

Этот рецепт реализует базовый кластер Raft в памяти, создает конечный
автомат, который может переходить между определенными
разрешенными состояниями, и подключает распределенный конечный
автомат к веб-обработчику, который может инициировать переход. Это
может быть полезно при реализации базового интерфейса конечного
автомата, который требуется для Raft, или при тестировании. Этот
рецепт использует https://github.com/hashicorp/raft для базовой
реализации Raft.

Как это сделать…
Следующие шаги охватывают написание и запуск вашего приложения:
В терминале или консольном приложении создайте новый каталог
с именем ~/projects/go-programmingcookbook/chapter11/consensus и перейдите к нему.
Выполните следующую команду:
$ go mod init github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter11/consensus
Вы должны увидеть файл с именем go.mod, который содержит
следующее:
module github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter11/consensus
Скопируйте тесты из ~/projects/go-programming-cookbookoriginal/chapter11/consensus или используйте это как
возможность написать свой собственный код!
Создайте файл с именем state.go со следующим содержимым:
package consensus
type state string
const (
first state = "first"
second = "second"
third = "third"
)
var allowedState map[state][]state

Tlgm: @it_boooks

func init() {
// setup valid states
allowedState = make(map[state][]state)
allowedState[first] = []state{second, third}
allowedState[second] = []state{third}
allowedState[third] = []state{first}
}
// CanTransition checks if a new state is valid
func (s *state) CanTransition(next state) bool {
for _, n := range allowedState[*s] {
if n == next {
return true
}
}
return false
}
// Transition will move a state to the next
// state if able
func (s *state) Transition(next state) {
if s.CanTransition(next) {
*s = next
}
}
Создайте файл с именем raftset.go со следующим содержимым:
package consensus
import (
"fmt"
"github.com/hashicorp/raft"
)
// keep a map of rafts for later
var rafts map[raft.ServerAddress]*raft.Raft
func init() {
rafts = make(map[raft.ServerAddress]*raft.Raft)
}

Tlgm: @it_boooks

// raftSet stores all the setup material we need
type raftSet struct {
Config *raft.Config
Store *raft.InmemStore
SnapShotStore raft.SnapshotStore
FSM *FSM
Transport raft.LoopbackTransport
Configuration raft.Configuration
}
// generate n raft sets to bootstrap the raft cluster
func getRaftSet(num int) []*raftSet {
rs := make([]*raftSet, num)
servers := make([]raft.Server, num)
for i := 0; i < num; i++ {
addr := raft.ServerAddress(fmt.Sprint(i))
_, transport := raft.NewInmemTransport(addr)
servers[i] = raft.Server{
Suffrage: raft.Voter,
ID: raft.ServerID(addr),
Address: addr,
}
config := raft.DefaultConfig()
config.LocalID = raft.ServerID(addr)

}

rs[i] = &raftSet{
Config: config,
Store: raft.NewInmemStore(),
SnapShotStore: raft.NewInmemSnapshotStore(),
FSM: NewFSM(),
Transport: transport,
}

// configuration needs to be consistent between
// services and so we need the full serverlist in this
// case
for _, r := range rs {
r.Configuration = raft.Configuration{Servers:
servers}
}

Tlgm: @it_boooks

return rs
}
Создайте файл с именем config.go со следующим содержимым:
package consensus
import (
"github.com/hashicorp/raft"
)
// Config creates num in-memory raft
// nodes and connects them
func Config(num int) {
// create n "raft-sets" consisting of
// everything needed to represent a node
rs := getRaftSet(num)
//connect all of the transports
for _, r1 := range rs {
for _, r2 := range rs {
r1.Transport.Connect(r2.Transport.LocalAddr(),
r2.Transport)
}
}
// for each node, bootstrap then connect
for _, r := range rs {
if err := raft.BootstrapCluster(r.Config, r.Store,
r.Store, r.SnapShotStore, r.Transport, r.Configuration);
err != nil {
panic(err)
}
raft, err := raft.NewRaft(r.Config, r.FSM, r.Store,
r.Store, r.SnapShotStore, r.Transport)
if err != nil {
panic(err)
}
rafts[r.Transport.LocalAddr()] = raft
}
}
Создайте файл с именем fsm.go со следующим содержимым:

Tlgm: @it_boooks

package consensus
import (
"io"
)

"github.com/hashicorp/raft"

// FSM implements the raft FSM interface
// and holds a state
type FSM struct {
state state
}
// NewFSM creates a new FSM with
// start state of "first"
func NewFSM() *FSM {
return &FSM{state: first}
}
// Apply updates our FSM
func (f *FSM) Apply(r *raft.Log) interface{} {
f.state.Transition(state(r.Data))
return string(f.state)
}
// Snapshot needed to satisfy the raft FSM
interface
func (f *FSM) Snapshot() (raft.FSMSnapshot, error)
{
return nil, nil
}
// Restore needed to satisfy the raft FSM interface
func (f *FSM) Restore(io.ReadCloser) error {
return nil
}
Создайте файл с именем handler.go со следующим содержимым:
package consensus
import (
"net/http"

Tlgm: @it_boooks

)

"time"

// Handler grabs the get param ?next= and tries
// to transition to the state contained there
func Handler(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
state := r.FormValue("next")
for address, raft := range rafts {
if address != raft.Leader() {
continue
}
result := raft.Apply([]byte(state), 1*time.Second)
if result.Error() != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
newState, ok := result.Response().(string)
if !ok {
w.WriteHeader(http.StatusInternalServerError)
return
}
if newState != state {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("invalid transition"))
return
}
w.WriteHeader(http.StatusOK)
w.Write([]byte(newState))
return
}
}
Создайте новый каталог с именем example и перейдите к нему.
Создайте файл с именем main.go со следующим содержимым:
package main
import (
"net/http"
"github.com/PacktPublishing/

Tlgm: @it_boooks

Go-Programming-Cookbook-Second-Edition/
chapter11/consensus"
)
func main() {
consensus.Config(3)
http.HandleFunc("/", consensus.Handler)
err := http.ListenAndServe(":3333", nil)
panic(err)
}
Запустите команду go run main.go. Alternatively, Вы также можете
запустить следующие команды:
$ go build
$ ./example
Вы также можете запустить следующие команды:
$ go run main.go
2019/05/04 21:06:46 [INFO] raft: Initial configuration
(index=1): [{Suffrage:Voter ID:0 Address:0}
{Suffrage:Voter ID:1 Address:1} {Suffrage:Voter ID:2
Address:2}]
2019/05/04 21:06:46 [INFO] raft: Initial configuration
(index=1): [{Suffrage:Voter ID:0 Address:0}
{Suffrage:Voter ID:1 Address:1} {Suffrage:Voter ID:2
Address:2}]
2019/05/04 21:06:46 [INFO] raft: Node at 0 [Follower]
entering Follower state (Leader: "")
2019/05/04 21:06:46 [INFO] raft: Node at 1 [Follower]
entering Follower state (Leader: "")
2019/05/04 21:06:46 [INFO] raft: Initial configuration
(index=1): [{Suffrage:Voter ID:0 Address:0}
{Suffrage:Voter ID:1 Address:1} {Suffrage:Voter ID:2
Address:2}]
2019/05/04 21:06:46 [INFO] raft: Node at 2 [Follower]
entering Follower state (Leader: "")
2019/05/04 21:06:47 [WARN] raft: Heartbeat timeout from
"" reached, starting election
2019/05/04 21:06:47 [INFO] raft: Node at 0 [Candidate]
entering Candidate state in term 2
2019/05/04 21:06:47 [DEBUG] raft: Votes needed: 2

Tlgm: @it_boooks

2019/05/04 21:06:47 [DEBUG] raft: Vote granted from 0 in
term 2. Tally: 1
2019/05/04 21:06:47 [DEBUG] raft: Vote granted from 1 in
term 2. Tally: 2
2019/05/04 21:06:47 [INFO] raft: Election won. Tally: 2
2019/05/04 21:06:47 [INFO] raft: Node at 0 [Leader]
entering Leader state
2019/05/04 21:06:47 [INFO] raft: Added peer 1, starting
replication
2019/05/04 21:06:47 [INFO] raft: Added peer 2, starting
replication
2019/05/04 21:06:47 [INFO] raft: pipelining replication
to peer {Voter 1 1}
2019/05/04 21:06:47 [INFO] raft: pipelining replication
to peer {Voter 2 2}
В отдельном терминале выполните следующую команду:
$ curl "http://localhost:3333/?next=second"
second
$ curl "http://localhost:3333/?next=third"
third
$ curl "http://localhost:3333/?next=second"
invalid transition
$ curl "http://localhost:3333/?next=first"
first
Файл go.mod может быть обновлен, и теперь файл go.sum должен
присутствовать в каталоге рецептов верхнего уровня.
Если вы скопировали или написали свои собственные тесты,
перейдите на один каталог вверх и запустите go test. Убедитесь,
что все тесты пройдены.

Как это работает…
Когда приложение запускается, мы инициализируем несколько объектов
Raft. Каждый из них имеет свой адрес и транспорт. Функция
InmemTransport{} также предоставляет метод для подключения других
транспортов и называется Connect(). Как только эти соединения
установлены, кластер Raft проводит выборы. При общении в кластере

Tlgm: @it_boooks

Raft клиенты должны общаться с лидером. В нашем случае один
обработчик может общаться со всеми узлами, поэтому обработчик
отвечает за вызов объекта Apply() лидера Raft. Это, в свою очередь,
запускает apply() на всех остальных узлах.
Функция InmemTransport{} упрощает процесс выбора и начальной
загрузки, позволяя всему находиться в памяти. На практике это не очень
полезно, за исключением тестирования и проверки концепций,
поскольку горутины могут свободно обращаться к общей памяти. Более
ориентированная на производство реализация будет использовать чтото вроде HTTP-транспорта, чтобы экземпляры службы могли
обмениваться данными между машинами. Для этого может
потребоваться дополнительный учет или обнаружение службы,
поскольку экземпляры службы должны прослушивать и обслуживать, а
также иметь возможность обнаруживать и устанавливать соединения
друг с другом.

Использование контейнеризации с
Docker
Docker — это контейнерная технология для упаковки и доставки
приложений. Другие преимущества включают переносимость,
поскольку контейнер будет работать одинаково независимо от ОС хоста.
Он предоставляет множество преимуществ виртуальной машины, но в
более легком контейнере. Можно ограничить потребление ресурсов
отдельными контейнерами и изолировать вашу среду. Может быть
чрезвычайно полезно иметь общую среду для ваших приложений
локально и когда вы отправляете свой код в производство. Docker
написан на Go и имеет открытый исходный код, поэтому
воспользоваться преимуществами клиента и библиотек просто. Этот
рецепт настроит контейнер Docker для базового приложения Go,
сохранит некоторую информацию о версии контейнера и
продемонстрирует обращение к обработчику из конечной точки Docker.

Подготовка
Настройте среду в соответствии с этими шагами:

Tlgm: @it_boooks

Обратитесь к разделу «Технические требования» в этой главе,
чтобы узнать, как настроить вашу среду.
Установите Docker с https://docs.docker.com/install. Это также будет
включать Docker Compose.

Как это сделать…
Эти шаги охватывают написание и запуск вашего приложения:
В терминале или консольном приложении создайте новый каталог
с именем ~/projects/go-programmingcookbook/chapter11/docker и перейдите к нему.
Выполните следующую команду:
$ go mod init github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter11/docker
Вы должны увидеть файл с именем go.mod, который содержит
следующее:
module github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter11/docker
Скопируйте тесты из ~/projects/go-programming-cookbookoriginal/chapter11/docker или используйте это как возможность
написать свой собственный код!
Создайте файл с именем dockerfile со следующим содержимым:
FROM alpine
ADD ./example/example /example
EXPOSE 8000
ENTRYPOINT /example
Создайте файл с именем setup.sh со следующим содержимым:
#!/usr/bin/env bash
pushd example
env GOOS=linux go build -ldflags "-X
main.version=1.0 -X
main.builddate=$(date +%s)"
popd

Tlgm: @it_boooks

docker build . -t example
docker run -d -p 8000:8000 example
Создайте файл с именем version.go со следующим содержимым:
package docker
import (
"encoding/json"
"net/http"
"time"
)
// VersionInfo holds artifacts passed in
// at build time
type VersionInfo struct {
Version string
BuildDate time.Time
Uptime time.Duration
}
// VersionHandler writes the latest version info
func VersionHandler(v *VersionInfo)
http.HandlerFunc {
t := time.Now()
return func(w http.ResponseWriter, r
*http.Request) {
v.Uptime = time.Since(t)
vers, err := json.Marshal(v)
if err != nil {
w.WriteHeader
(http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
w.Write(vers)
}
}
Создайте новый каталог с именем example и перейдите к нему.
Создайте файл с именем main.go со следующим содержимым:
package main

Tlgm: @it_boooks

import (
"fmt"
"net/http"
"strconv"
"time"
"github.com/PacktPublishing/
Go-Programming-Cookbook-Second-Edition/
chapter11/docker"
)
// these are set at build time
var (
version string
builddate string
)
var versioninfo docker.VersionInfo
func init() {
// parse buildtime variables
versioninfo.Version = version
i, err := strconv.ParseInt(builddate, 10,
64)
if err != nil {
panic(err)
}
tm := time.Unix(i, 0)
versioninfo.BuildDate = tm
}
func main() {
http.HandleFunc("/version",
docker.VersionHandler(&versioninfo))
fmt.Printf("version %s listening on :8000\n",
versioninfo.Version)
panic(http.ListenAndServe(":8000", nil))
}
Вернитесь в исходный каталог.
Выполните следующую команду:
$ bash setup.sh

Tlgm: @it_boooks

Вы также можете запустить следующие команды:
$ bash setup.sh
~/go/src/github.com/PacktPublishing/Go-ProgrammingCookbookSecond-Edition/chapter11/docker/example
~/go/src/github.com/PacktPublishing/Go-ProgrammingCookbookSecond-Edition/chapter11/docker
~/go/src/github.com/PacktPublishing/Go-ProgrammingCookbookSecond-Edition/chapter11/docker
Sending build context to Docker daemon 6.031 MB
Step 1/4 : FROM alpine
---> 4a415e366388
Step 2/4 : ADD ./example/example /example
---> de34c3c5451e
Removing intermediate container bdcd9c4f4381
Step 3/4 : EXPOSE 8000
---> Running in 188f450d4e7b
---> 35d1a2652b43
Removing intermediate container 188f450d4e7b
Step 4/4 : ENTRYPOINT /example
---> Running in cf0af4f48c3a
---> 3d737fc4e6e2
Removing intermediate container cf0af4f48c3a
Successfully built 3d737fc4e6e2
b390ef429fbd6e7ff87058dc82e15c3e7a8b2e
69a601892700d1d434e9e8e43b
Выполните следующие команды:
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b390ef429fbd example "/bin/sh -c /example" 22 seconds ago
Up 23
seconds 0.0.0.0:8000->8000/tcp optimistic_wescoff
$ curl localhost:8000/version
{"Version":"1.0","BuildDate":"2017-0430T21:55:56Z","Uptime":48132111264}
$docker kill optimistic_wescoff # grab from first output
optimistic_wescoff

Tlgm: @it_boooks

Файл go.mod может быть обновлен, и теперь файл go.sum должен
присутствовать в каталоге рецептов верхнего уровня.
Если вы скопировали или написали свои собственные тесты,
перейдите на один каталог вверх и запустите go test. Убедитесь,
что все тесты пройдены.

Как это работает…
Этот рецепт создал сценарий, который компилирует двоичный файл Go
для архитектуры Linux и устанавливает различные частные переменные
в main.go. Эти переменные используются для возврата информации о
версии конечной точки версии. После компиляции двоичного файла
создается контейнер Docker, содержащий двоичный файл. Это
позволяет нам использовать очень маленькие образы контейнеров,
поскольку среда выполнения Go является автономной в двоичном
формате. Затем мы запускаем контейнер, открывая порт, на котором
контейнер прослушивает HTTP-трафик. Наконец, мы curl порт на
локальном хосте и видим, что возвращается информация о нашей
версии.

Стратегии оркестрации и
развертывания
Docker значительно упрощает оркестрацию и развертывание. В этом
рецепте мы настроим соединение с MongoDB, а затем вставим
документ и запросим все это из контейнеров Docker. Этот рецепт
настроит ту же среду, что и рецепт «Использование NoSQL с MongoDB и
mgo» из Главы 6 «Все о базах данных и хранилище», но запустит
приложение и среду внутри контейнеров и будет использовать Docker
Compose для оркестровки и подключения к ним.
Позже это можно использовать в сочетании с Docker Swarm,
интегрированным инструментом Docker, который позволяет вам
управлять кластером, создавать и развертывать узлы, которые можно
легко увеличивать или уменьшать, а также управлять балансировкой
нагрузки (https://docs.docker.com/engine/swarm/). Еще одним хорошим
примером оркестрации контейнеров является Kubernetes (https://kuberne

Tlgm: @it_boooks

tes.io/), фреймворк для оркестровки контейнеров, написанный Google с
использованием языка программирования Go.

Как это сделать…
Следующие шаги охватывают написание и запуск вашего приложения:
В терминале или консольном приложении создайте новый каталог
с именем ~/projects/go-programmingcookbook/chapter11/orchestrate и перейдите к нему.
Выполните следующую команду:
$ go mod init github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter11/orchestrate
Вы должны увидеть файл с именем go.mod, который содержит
следующее:
module github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter11/orchestrate
Скопируйте тесты из ~/projects/go-programming-cookbookoriginal/chapter11/orchestrate или используйте это как
возможность написать свой собственный код!
Создайте файл с именем dockerfile со следующим содержимым:
FROM golang:1.12.4-alpine3.9
ENV GOPATH /code/
ADD . /code/src/github.com/PacktPublishing/GoProgramming-Cookbook-Second-Edition/chapter11/docker
WORKDIR /code/src/github.com/PacktPublishing/GoProgramming-Cookbook-SecondEdition/chapter11/docker/example
RUN GO111MODULE=on GOPROXY=off go build -mod=vendor
ENTRYPOINT /code/src/github.com/PacktPublishing/GoProgramming-Cookbook-SecondEdition/chapter11/docker/example/example
Создайте файл с именем docker-compose.yml со следующим
содержимым:

Tlgm: @it_boooks

version: '2'
services:
app:
build: .
mongodb:
image: "mongo:latest"
Создайте файл с именем config.go со следующим содержимым:
package mongodb
import (
"context"
"fmt"
"time"
"github.com/mongodb/mongo-go-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
// Setup initializes a mongo client
func Setup(ctx context.Context, address string)
(*mongo.Client, error) {
ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
defer cancel()
fmt.Println(address)
client, err :=
mongo.NewClient(options.Client().ApplyURI(address))
if err != nil {
return nil, err
}
if err := client.Connect(ctx); err != nil {
return nil, err
}
return client, nil
}
Создайте файл с именем exec.go со следующим содержимым:
package mongodb

Tlgm: @it_boooks

import (
"context"
"fmt"
"github.com/mongodb/mongo-go-driver/bson"
)
// State is our data model
type State struct {
Name string `bson:"name"`
Population int `bson:"pop"`
}
// Exec creates then queries an Example
func Exec(address string) error {
ctx := context.Background()
db, err := Setup(ctx, address)
if err != nil {
return err
}
conn := db.Database("gocookbook").Collection("example")
vals := []interface{}{&State{"Washington", 7062000},
&State{"Oregon", 3970000}}
// we can inserts many rows at once
if _, err := conn.InsertMany(ctx, vals); err != nil {
return err
}
var s State
if err := conn.FindOne(ctx, bson.M{"name":
"Washington"}).Decode(&s); err != nil {
return err
}
if err := conn.Drop(ctx); err != nil {
return err
}
fmt.Printf("State: %#v\n", s)

Tlgm: @it_boooks

return nil
}
Создайте новый каталог с именем example и перейдите к нему.
Создайте файл с именем main.go со следующим содержимым:
package main
import mongodb "github.com/PacktPublishing/GoProgramming-Cookbook-SecondEdition/chapter11/orchestrate"
func main() {
if err := mongodb.Exec("mongodb://mongodb:27017"); err
!= nil {
panic(err)
}
}
Вернитесь в исходный каталог.
Запустите команду go mod vendor.
Запустите команду docker-compose up -d.
Run the docker logs orchestrate_app_1 command. You should
now see the following output:
$ docker logs orchestrate_app_1
State: docker.State{Name:"Washington",
Population:7062000}
Файл go.mod может быть обновлен, и теперь файл go.sum должен
присутствовать в каталоге рецептов верхнего уровня.
Если вы скопировали или написали свои собственные тесты,
перейдите на один каталог вверх и запустите go test. Убедитесь,
что все тесты пройдены.

Как это работает…
Эта конфигурация хороша для локальной разработки. После запуска
команды docker-compose up локальный каталог перестраивается,
Docker устанавливает соединение с экземпляром MongoDB, используя
последнюю версию, и начинает работать с ним. В этом рецепте для
управления зависимостями используется go mod vendor. В результате

Tlgm: @it_boooks

мы отключаем go mod cache и сообщаем команде go build
использовать созданный нами каталог vendor.
Это может стать хорошей отправной точкой при запуске приложений,
требующих подключения к внешним службам; все рецепты в Главе 6
«Все о базах данных и хранилищах» могут использовать этот подход, а
не создавать локальный экземпляр базы данных. Для производства вы,
вероятно, не захотите запускать хранилище данных за контейнером
Docker, но у вас также обычно будут статические имена хостов для
настройки.

Приложения для мониторинга
Существует множество способов мониторинга приложений Go. Один из
самых простых способов — настроить Prometheus, приложение для
мониторинга, написанное на Go (https://prometheus.io). Это приложение,
которое опрашивает конечную точку на основе вашего файла
конфигурации и собирает много информации о вашем приложении,
включая количество горутин, использование памяти и многое другое.
Это приложение будет использовать методы из предыдущего рецепта
для настройки среды Docker для размещения Prometheus и подключения
к нему.

Как это сделать…
Следующие шаги охватывают написание и запуск вашего приложения:
В терминале или консольном приложении создайте новый каталог
с именем ~/projects/go-programmingcookbook/chapter11/monitoring и перейдите в него.
Выполните следующую команду:
$ go mod init github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter11/monitoring
Вы должны увидеть файл с именем go.mod, который содержит
следующее:
module github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter11/monitoring

Tlgm: @it_boooks

Скопируйте тесты из ~/projects/go-programming-cookbookoriginal/chapter11/monitoring или используйте это как
возможность написать свой собственный код!
Создайте файл с именем dockerfile со следующим содержимым:
FROM golang:1.12.4-alpine3.9
ENV GOPATH /code/
ADD . /code/src/github.com/agtorre/gocookbook/chapter11/monitoring
WORKDIR /code/src/github.com/agtorre/gocookbook/chapter11/monitoring
RUN GO111MODULE=on GOPROXY=off go build -mod=vendor
ENTRYPOINT /code/src/github.com/agtorre/gocookbook/chapter11/monitoring/monitoring
Создайте файл с именем docker-compose.yml со следующим
содержимым:
version: '2'
services:
app:
build: .
prometheus:
ports:
- 9090:9090
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
image: "prom/prometheus"
Создайте файл с именем main.go со следующим содержимым:
package main
import (
"net/http"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
func main() {
http.Handle("/metrics", promhttp.Handler())

Tlgm: @it_boooks

panic(http.ListenAndServe(":80", nil))
}
Создайте файл с именем prometheus.yml со следующим
содержимым:
global:
scrape_interval: 15s # By default, scrape targets
every 15
seconds.
# A scrape configuration containing exactly one
endpoint to
scrape:
# Here it's Prometheus itself.
scrape_configs:
# The job name is added as a label `job=
` to any
timeseries scraped from this config.
- job_name: 'app'
# Override the global default and scrape targets
from this job
every 5 seconds.
scrape_interval: 5s
static_configs:
- targets: ['app:80']
Запустите команду go mod vendor.
Запустите команду docker-compose up. Теперь вы должны увидеть
следующий вывод:
$ docker-compose up
Starting monitoring_prometheus_1 ... done
Starting monitoring_app_1 ... done
Attaching to monitoring_app_1, monitoring_prometheus_1
prometheus_1 | time="2019-05-05T03:10:25Z" level=info
msg="Starting prometheus (version=1.6.1, branch=master,
revision=4666df502c0e239ed4aa1d80abbbfb54f61b23c3)"
source="main.go:88"
prometheus_1 | time="2019-05-05T03:10:25Z" level=info
msg="Build context (go=go1.8.1, user=root@7e45fa0366a7,
date=20170419-14:32:22)" source="main.go:89"

Tlgm: @it_boooks

prometheus_1 | time="2019-05-05T03:10:25Z" level=info
msg="Loading configuration file
/etc/prometheus/prometheus.yml" source="main.go:251"
prometheus_1 | time="2019-05-05T03:10:25Z" level=info
msg="Loading series map and head chunks..."
source="storage.go:421"
prometheus_1 | time="2019-05-05T03:10:25Z" level=warning
msg="Persistence layer appears dirty."
source="persistence.go:846"
prometheus_1 | time="2019-05-05T03:10:25Z" level=warning
msg="Starting crash recovery. Prometheus is inoperational
until complete." source="crashrecovery.go:40"
prometheus_1 | time="2019-05-05T03:10:25Z" level=warning
msg="To avoid crash recovery in the future, shut down
Prometheus with SIGTERM or a HTTP POST to /-/quit."
source="crashrecovery.go:41"
prometheus_1 | time="2019-05-05T03:10:25Z" level=info
msg="Scanning files." source="crashrecovery.go:55"
prometheus_1 | time="2019-05-05T03:10:25Z" level=info
msg="File scan complete. 43 series found."
source="crashrecovery.go:83"
prometheus_1 | time="2019-05-05T03:10:25Z" level=info
msg="Checking for series without series file."
source="crashrecovery.go:85"
prometheus_1 | time="2019-05-05T03:10:25Z" level=info
msg="Check for series without series file complete."
source="crashrecovery.go:131"
prometheus_1 | time="2019-05-05T03:10:25Z" level=info
msg="Cleaning up archive indexes."
source="crashrecovery.go:411"
prometheus_1 | time="2019-05-05T03:10:25Z" level=info
msg="Clean-up of archive indexes complete."
source="crashrecovery.go:504"
prometheus_1 | time="2019-05-05T03:10:25Z" level=info
msg="Rebuilding label indexes."
source="crashrecovery.go:512"
prometheus_1 | time="2019-05-05T03:10:25Z" level=info
msg="Indexing metrics in memory."
source="crashrecovery.go:513"
prometheus_1 | time="2019-05-05T03:10:25Z" level=info
msg="Indexing archived metrics."
source="crashrecovery.go:521"
prometheus_1 | time="2019-05-05T03:10:25Z" level=info

Tlgm: @it_boooks

msg="All requests for rebuilding the label indexes
queued. (Actual processing may lag behind.)"
source="crashrecovery.go:540"
prometheus_1 | time="2019-05-05T03:10:25Z" level=warning
msg="Crash recovery complete."
source="crashrecovery.go:153"
prometheus_1 | time="2019-05-05T03:10:25Z" level=info
msg="43 series loaded." source="storage.go:432"
prometheus_1 | time="2019-05-05T03:10:25Z" level=info
msg="Starting target manager..."
source="targetmanager.go:61"
prometheus_1 | time="2019-05-05T03:10:25Z" level=info
msg="Listening on :9090" source="web.go:259"
Файл go.mod может быть обновлен, и теперь файл go.sum должен
присутствовать в каталоге рецептов верхнего уровня.
Перейдите в браузере по адресу http://localhost:9090/. Вы
должны увидеть множество показателей, связанных с вашим
приложением!

Как это работает…
Этот рецепт создает простой обработчик в Go, который экспортирует
статистику о запущенном приложении в prometheus с помощью
prometheus go клиента. Мы подключаем наше приложение к серверу
prometheus, работающему в докере, и обрабатываем сетевое
подключение и запуск с помощью docker-compose. Параметры частоты
сбора данных, порт, который обслуживает приложение, и имя
приложения указаны в файле prometheus.yml. После запуска обоих
контейнеров сервер prometheus начинает сбор и мониторинг
приложения на указанном порту. Он также предоставляет вебинтерфейс, который мы посещаем в браузере, чтобы увидеть больше
информации о нашем приложении.
Обработчик клиента Prometheus возвращает различные статистические
данные о вашем приложении на сервер Prometheus. Это позволяет
указать несколько серверов Prometheus на приложение без
необходимости перенастраивать или развертывать приложение.
Большинство этих статистических данных являются общими и
полезными для таких вещей, как обнаружение утечек памяти. Многие
другие решения требуют от вас периодической отправки информации

Tlgm: @it_boooks

на сервер. Следующий рецепт, «Сбор метрик», продемонстрирует, как
отправлять пользовательские метрики на сервер Prometheus.

Сбор метрик
В дополнение к общей информации о вашем приложении может быть
полезно выдавать метрики, специфичные для приложения. Например,
мы можем захотеть собрать данные о времени или отслеживать, сколько
раз происходит событие.
Этот рецепт будет использовать пакет github.com/rcrowley/gometrics для сбора метрик и предоставления их через конечную точку.
Существуют различные инструменты экспорта, которые вы можете
использовать для экспорта метрик в такие места, как Prometheus и
InfluxDB, которые также написаны на Go.

Подготовка
Настройте среду в соответствии с этими шагами:
Обратитесь к разделу «Технические требования» в этой главе,
чтобы узнать, как настроить вашу среду.
Запустите команду go get github.com/rcrowley/go-metrics.

Как это сделать…
Эти шаги охватывают написание и запуск вашего приложения:
В терминале или консольном приложении создайте новый каталог
с именем ~/projects/go-programmingcookbook/chapter11/metrics и перейдите в него.
Выполните следующую команду:
$ go mod init github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter11/metrics
Вы должны увидеть файл с именем go.mod, который содержит
следующее:
module github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter11/metrics

Tlgm: @it_boooks

Скопируйте тесты из ~/projects/go-programming-cookbookoriginal/chapter11/metrics или используйте это как
возможность написать свой собственный код!
Создайте файл с именем handler.go со следующим содержимым:
package metrics
import (
"net/http"
"time"
metrics "github.com/rcrowley/go-metrics"
)
// CounterHandler will update a counter each time
it's called
func CounterHandler(w http.ResponseWriter, r
*http.Request) {
c :=
metrics.GetOrRegisterCounter("counterhandler.counter",
nil)
c.Inc(1)

}

w.WriteHeader(http.StatusOK)
w.Write([]byte("success"))

// TimerHandler records the duration required to
compelete
func TimerHandler(w http.ResponseWriter, r
*http.Request) {
currt := time.Now()
t :=
metrics.GetOrRegisterTimer("timerhandler.timer", nil)
w.WriteHeader(http.StatusOK)
w.Write([]byte("success"))
t.UpdateSince(currt)
}
Создайте файл с именем report.go со следующим содержимым:

Tlgm: @it_boooks

package metrics
import (
"net/http"
gometrics "github.com/rcrowley/go-metrics"
)
// ReportHandler will emit the current metrics in
json format
func ReportHandler(w http.ResponseWriter, r
*http.Request) {
w.WriteHeader(http.StatusOK)
t := gometrics.GetOrRegisterTimer(
"reporthandler.writemetrics", nil)
t.Time(func() {
gometrics.WriteJSONOnce(gometrics.DefaultRegistry, w)
})
}
Создайте новый каталог с именем example и перейдите к нему.
Создайте файл с именем main.go:
package main
import (
"net/http"
"github.com/PacktPublishing/
Go-Programming-Cookbook-Second-Edition/
chapter11/metrics"
)
func main() {
// handler to populate metrics
http.HandleFunc("/counter",
metrics.CounterHandler)
http.HandleFunc("/timer", metrics.TimerHandler)
http.HandleFunc("/report",
metrics.ReportHandler)
fmt.Println("listening on :8080")

Tlgm: @it_boooks

panic(http.ListenAndServe(":8080", nil))
}
Выполните go run main.go. Alternatively, Вы также можете
запустить следующую команду:
$ go build
$ ./example
Вы также можете запустить следующие команды:
$ go run main.go
listening on :8080
Запустите следующие команды из отдельной оболочки:
$ curl localhost:8080/counter
success
$ curl localhost:8080/timer
success
$ curl localhost:8080/report
{"counterhandler.counter":{"count":1},
"reporthandler.writemetrics":
{"15m.rate":0,"1m.rate":0,"5m.rate":0,"75%":0,"95%":0,"99
%":0,"99.9%":0,"count":0,"max":0,"mean":0,"mean.rate":0,"
median":0,"min":0,"stddev":0},"timerhandler.timer":
{"15m.rate":0.0011080303990206543,"1m.rate":0.01599111707
4135343,"5m.rate":0.0033057092356765017,"75%":60485,"95%"
:60485,"99%":60485,"99.9%":60485,"count":1,"max":60485,"m
ean":60485,"mean.rate":1.1334543719787356,"median":60485,
"min":60485,"stddev":0}}
Попробуйте нажать все конечные точки еще несколько раз, чтобы
посмотреть, как они изменятся.
Файл go.mod может быть обновлен, и теперь файл go.sum должен
присутствовать в каталоге рецептов верхнего уровня.
Если вы скопировали или написали свои собственные тесты,
перейдите на один каталог вверх и запустите go test. Убедитесь,
что все тесты пройдены.

Как это работает…

Tlgm: @it_boooks

gometrics хранит все ваши показатели в реестре. После настройки вы
можете использовать любой из параметров генерации метрик, например
counter или timer, и он сохранит это обновление в реестре.
Существует несколько экспортеров, которые экспортируют метрики в
сторонние инструменты. В нашем случае мы настроили обработчик,
который выдает все метрики в формате JSON.
Мы настроили три обработчика: один увеличивает счетчик, другой
записывает время до выхода из обработчика и третий печатает отчет
(одновременно увеличивая дополнительный счетчик). Функции
GetOrRegister полезны для атомарного получения или создания
генератора метрик, если он в настоящее время не существует
потокобезопасным способом. Кроме того, вы можете зарегистрировать
все один раз заранее.

Tlgm: @it_boooks

12. Реактивное
программирование и потоки
данных
В этой главе мы обсудим шаблоны проектирования реактивного
программирования в Go. Реактивное программирование — это
концепция программирования, которая фокусируется на потоках
данных и распространении изменений. Такие технологии, как Kafka,
позволяют быстро создавать или потреблять поток данных. В
результате эти технологии естественным образом подходят друг другу.
В рецепте «Подключение Kafka к Goflow» мы рассмотрим объединение
очереди сообщений kafka с goflow, чтобы показать практический
пример использования этих технологий. В этой главе также будут
рассмотрены различные способы подключения к Kafka и
использования его для обработки сообщений. Наконец, в этой главе
будет показано, как создать базовый сервер graphql в Go.
В этой главе мы рассмотрим следующие рецепты:
Использование Goflow для программирования потока данных
Использование Kafka с Sarama
Использование асинхронных продюсеров с Kafka
Подключение Kafka к Goflow
Пишем сервер GraphQL на Go

Технические требования
Чтобы продолжить выполнение всех рецептов в этой главе, настройте
свою среду в соответствии со следующими шагами:
Загрузите и установите Go 1.12.6 или более позднюю версию в
своей операционной системе с https://golang.org/doc/install.
Откройте терминал или консольное приложение, создайте и
перейдите в каталог проекта, например ~/projects/goprogramming-cookbook. Весь код будет запускаться и изменяться
из этого каталога.

Tlgm: @it_boooks

Скопируйте последний код в ~/projects/go-programmingcookbook-original и, при желании, работайте из этого каталога,
вместо того, чтобы вводить примеры вручную:
$ git clone git@github.com:PacktPublishing/GoProgramming-Cookbook-Second-Edition.git go-programmingcookbook-original

Использование Go ow для
программирования потока данных
Пакет github.com/trustmaster/goflow полезен для создания
приложений на основе потока данных. Он пытается абстрагировать
концепции, чтобы вы могли писать компоненты и соединять их вместе
с помощью пользовательской сети. Этот рецепт воссоздает
приложение, описанное в Главt 9, «Тестирование кода Go», но сделает
это с использованием пакета goflow.

Как это сделать…
Эти шаги охватывают написание и запуск вашего приложения:
В терминале или консольном приложении создайте новый каталог
с именем ~/projects/go-programmingcookbook/chapter12/goflow и перейдите в этот каталог.
Выполните следующую команду:
$ go mod init github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter12/goflow
Вы должны увидеть файл с именем go.mod, который содержит
следующее:
module github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter12/goflow
Скопируйте тесты из ~/projects/go-programming-cookbookoriginal/chapter12/goflow или используйте это как упражнение
для написания собственного кода!
Создайте файл с именем components.go со следующим
содержимым:

Tlgm: @it_boooks

package goflow
import (
"encoding/base64"
"fmt"
)
// Encoder base64 encodes all input
type Encoder struct {
Val TWVzc2FnZTQ=
Message8 => TWVzc2FnZTg=
Message2 => TWVzc2FnZTI=
Message3 => TWVzc2FnZTM=
Message7 => TWVzc2FnZTc=
Message10 => TWVzc2FnZTEw
Message9 => TWVzc2FnZTk=
Message12 => TWVzc2FnZTEy
Message11 => TWVzc2FnZTEx
Message14 => TWVzc2FnZTE0
Message13 => TWVzc2FnZTEz
Message16 => TWVzc2FnZTE2
Message15 => TWVzc2FnZTE1
Message18 => TWVzc2FnZTE4
Message17 => TWVzc2FnZTE3
Message19 => TWVzc2FnZTE5
Файл go.mod может быть обновлен, и теперь файл go.sum должен
присутствовать в каталоге рецептов верхнего уровня.
Если вы скопировали или написали свои собственные тесты,
перейдите на один каталог вверх и запустите команду go test.
Убедитесь, что все тесты пройдены.

Tlgm: @it_boooks

Как это работает…
Пакет
github.com/trustmaster/goflow
работает,
определяя
network/graph, регистрируя некоторые компоненты, а затем связывая
их вместе. Это может показаться немного подверженным ошибкам,
поскольку компоненты описываются с помощью строк, но обычно это
приводит к сбою на ранней стадии выполнения, пока приложение не
настроено и не работает правильно.
В этом рецепте мы устанавливаем два компонента, один из которых
кодирует входящую строку с помощью Base64, а другой печатает все,
что ему передается. Мы подключаем его к входному каналу, который
инициализирован в main.go, и все, что передается на этот канал, будет
проходить через наш конвейер.
Большой упор в этом подходе делается на игнорирование внутренней
сути того, что происходит. Мы относимся ко всему как к
подключенному черному ящику и позволяем goflow делать все
остальное. Вы можете видеть в этом рецепте, насколько мал код для
выполнения этого конвейера задач и что у нас меньше ручек для
управления числом рабочих процессов, среди прочего.

Использование Kafka с Sarama
Kafka — популярная распределенная очередь сообщений с
множеством расширенных функций для построения распределенных
систем. Этот рецепт покажет, как писать в тему Kafka с помощью
синхронного продюсера и как использовать ту же тему с помощью
потребителя раздела. В этом рецепте не рассматриваются различные
конфигурации Kafka, так как это гораздо более широкая тема,
выходящая за рамки данной книги, но я предлагаю начать с https://kafk
a.apache.org/intro.

Подготовка
Настройте среду в соответствии с этими шагами:
Обратитесь к разделу «Технические требования» в этой главе,
чтобы узнать, как настроить вашу среду.

Tlgm: @it_boooks

Установите Kafka, выполнив шаги, указанные на странице https://
www.tutorialspoint.com/apache_kafka/apache_kafka_installation_step
s.htm.
Кроме того, вы также можете получить доступ к https://github.com/
spotify/docker-kafka.

Как это сделать…
Эти шаги охватывают написание и запуск вашего приложения:
В терминале или консольном приложении создайте новый каталог
с именем ~/projects/go-programmingcookbook/chapter12/synckafka и перейдите в этот каталог.
Запустите эту команду:
$ go mod init github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter12/synckafka
Вы должны увидеть файл с именем go.mod, который содержит
следующее:
module github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter12/synckafka
Скопируйте тесты из ~/projects/go-programming-cookbookoriginal/chapter12/synckafka или используйте это как
упражнение для написания собственного кода!
Убедитесь, что Kafka запущена и работает на localhost:9092.
Создайте файл с именем main.go в каталоге с именем consumer со
следующим содержимым:
package main
import (
"log"
)

sarama "github.com/Shopify/sarama"

func main() {
consumer, err :=
sarama.NewConsumer([]string{"localhost:9092"},

Tlgm: @it_boooks

nil)

if err != nil {
panic(err)
}
defer consumer.Close()
partitionConsumer, err :=
consumer.ConsumePartition("example", 0,
sarama.OffsetNewest)
if err != nil {
panic(err)
}
defer partitionConsumer.Close()

for {
msg := message sent to partition 0 at
> message sent to partition 0 at
> message sent to partition 0 at
> message sent to partition 0 at
> message sent to partition 0 at

Tlgm: @it_boooks

2017/05/07 11:50:38 > message sent to partition 0
offset 5
2017/05/07 11:50:38 > message sent to partition 0
offset 6
2017/05/07 11:50:38 > message sent to partition 0
offset 7
2017/05/07 11:50:38 > message sent to partition 0
offset 8
2017/05/07 11:50:38 > message sent to partition 0
offset 9
В consumer терминале вы должны увидеть следующее::

at
at
at
at
at

$ go run ./consumer
2017/05/07 11:50:38 Consumed message: "Message 0" at
offset: 0
2017/05/07 11:50:38 Consumed message: "Message 1" at
offset: 1
2017/05/07 11:50:38 Consumed message: "Message 2" at
offset: 2
2017/05/07 11:50:38 Consumed message: "Message 3" at
offset: 3
2017/05/07 11:50:38 Consumed message: "Message 4" at
offset: 4
2017/05/07 11:50:38 Consumed message: "Message 5" at
offset: 5
2017/05/07 11:50:38 Consumed message: "Message 6" at
offset: 6
2017/05/07 11:50:38 Consumed message: "Message 7" at
offset: 7
2017/05/07 11:50:38 Consumed message: "Message 8" at
offset: 8
2017/05/07 11:50:38 Consumed message: "Message 9" at
offset: 9
Файл go.mod может быть обновлен, и теперь файл go.sum должен
присутствовать в каталоге рецептов верхнего уровня.
Если вы скопировали или написали свои собственные тесты,
перейдите на один каталог вверх и запустите go test. Убедитесь,
что все тесты пройдены.

Как это работает…

Tlgm: @it_boooks

Этот рецепт демонстрирует передачу простых сообщений через Kafka.
Более сложные методы должны использовать формат сериализации,
такой как json, gob, protobuf или другие. Продюсер может отправить
сообщение Kafka синхронно через sendMessage. Это плохо работает в
случаях, когда кластер Kafka не работает, и может привести к
зависанию процесса в этих случаях. Это важно учитывать для таких
приложений, как веб-обработчики, поскольку это может привести к
тайм-аутам и жестким зависимостям от кластера Kafka.
Предполагая, что очереди сообщений правильны, наш потребитель
будет наблюдать за потоком Kafka и что-то делать с результатами.
Предыдущие рецепты в этой главе могут использовать этот поток для
выполнения дополнительной обработки.

Использование асинхронных
продюсеров с Kafka
Часто полезно не ждать, пока продюсер Kafka завершит работу,
прежде чем переходить к следующей задаче. В таких случаях вы
можете использовать асинхронного продюсера. Эти продюсеры
принимают сообщения Sarama на канале и имеют методы для возврата
канала успеха/ошибки, которые можно проверить отдельно.
В этом рецепте мы создадим подпрограмму Go, которая будет
обрабатывать сообщения об успехе и неудаче, в то время как мы
разрешаем обработчику ставитьсообщения в очередь для отправки
независимо от результата.

Подготовка
Обратитесь к разделу «Подготовка» рецепта «Использование Kafka с
Sarama».

Как это сделать…
Эти шаги охватывают написание и запуск вашего приложения:
В своем терминале или консольном приложении создайте новый
каталог с именем ~/projects/go-programmingcookbook/chapter12/asynckafka и перейдите в этот каталог.

Tlgm: @it_boooks

Запустите эту команду:
$ go mod init github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter12/asynckafka
Вы должны увидеть файл с именем go.mod, который содержит
следующее:
module github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter12/asynckafka
Скопируйте тесты из ~/projects/go-programming-cookbookoriginal/chapter12/asynckafka или используйте это как
упражнение для написания собственного кода!
Убедитесь, что Kafka запущена и работает на localhost:9092.
Скопируйте каталог consumer из предыдущего рецепта.
Создайте каталог с именем producer и перейдите к нему.
Создайте файл с именем producer.go со следующим
содержимым:
package main
import (
"log"
)

sarama "github.com/Shopify/sarama"

// Process response grabs results and errors from
a producer
// asynchronously
func ProcessResponse(producer
sarama.AsyncProducer) {
for {
select {
case result := message: "an" sent to partition 0
at offset 2
2017/05/07 13:53:29 > message: "example" sent to
partition 0 at offset 3
В терминале consumer вы должны увидеть следующее:
$ go run ./consumer
2017/05/07 13:52:54 Consumed message: "this" at offset: 0
2017/05/07 13:53:25 Consumed message: "is" at offset: 1
2017/05/07 13:53:27 Consumed message: "an" at offset: 2
2017/05/07 13:53:29 Consumed message: "example" at
offset: 3
Файл go.mod может быть обновлен, и теперь файл go.sum должен
присутствовать в каталоге рецептов верхнего уровня.
Если вы скопировали или написали свои собственные тесты,
перейдите на один каталог вверх и запустите go test. Убедитесь,
что все тесты пройдены.

Как это работает…
Все наши модификации в этой главе были сделаны продюсером. На
этот раз мы создали отдельную процедуру Go для обработки успехов и
ошибок. Если их оставить необработанными, ваше приложение
заблокируется. Затем мы привязываем нашего производителя к
обработчику и посылаем ему сообщения всякий раз, когда сообщение
получено, через вызов GET к обработчику.
Обработчик немедленно вернет успех после отправки сообщения,
независимо от его ответа. Если это неприемлемо, следует использовать
синхронный подход. В нашем случае мы согласны с последующей
обработкой успехов и ошибок отдельно.

Tlgm: @it_boooks

Наконец, мы curl нашу конечную точку с несколькими разными
сообщениями, и вы можете видеть, как они перетекают из обработчика
туда, где они в конечном итоге печатаются потребителем Kafka,
который мы написали в предыдущем разделе.

Подключение Kafka к Go ow
Этот рецепт объединит потребителя Kafka с конвейером Goflow. Когда
наш потребитель получает сообщения от Kafka, он запускает для них
string.ToUpper(), а затем выводит результаты. Они естественным
образом сочетаются друг с другом, поскольку Goflow предназначен
для работы с входящим потоком, что и предоставляет нам Kafka.

Подготовка
Обратитесь к разделу «Подготовка» рецепта «Использование Kafka с
Sarama».

Как это сделать…
Эти шаги охватывают написание и запуск вашего приложения:
В терминале или консольном приложении создайте новый каталог
с именем ~/projects/go-programmingcookbook/chapter12/kafkaflow и перейдите в этот каталог.
Запустите эту команду:
$ go mod init github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter12/kafkaflow
Вы должны увидеть файл с именем go.mod, который содержит
следующее:
module github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter12/kafkaflow
Скопируйте тесты из ~/projects/go-programming-cookbookoriginal/chapter12/kafkaflow или используйте это как
упражнение для написания собственного кода!
Убедитесь, что Kafka запущена и работает на localhost:9092.

Tlgm: @it_boooks

Создайте файл с именем components.go со следующим
содержимым:
package kafkaflow
import (
"fmt"
"strings"
flow "github.com/trustmaster/goflow"
)
// Upper upper cases the incoming
// stream
type Upper struct {
Val
partition 0 at offset
2017/05/07 18:24:12 >

message
0
message
1
message
2
message
3
message
4
message
5
message
6
message

"Message 0" sent to
"Message 1" sent to
"Message 2" sent to
"Message 3" sent to
"Message 4" sent to
"Message 5" sent to
"Message 6" sent to
"Message 7" sent to

Tlgm: @it_boooks

partition 0 at offset 7
2017/05/07 18:24:12 > message "Message 8" sent to
partition 0 at offset 8
2017/05/07 18:24:12 > message "Message 9" sent to
partition 0 at offset 9
В терминале consumer вы должны увидеть следующее:
$ go run ./consumer
MESSAGE 0
MESSAGE 1
MESSAGE 2
MESSAGE 3
MESSAGE 4
MESSAGE 5
MESSAGE 6
MESSAGE 7
MESSAGE 8
MESSAGE 9
Файл go.mod может быть обновлен, и теперь файл go.sum должен
присутствовать в каталоге рецептов верхнего уровня.
Если вы скопировали или написали свои собственные тесты,
перейдите на один каталог вверх и запустите go test. Убедитесь,
что все тесты пройдены.

Как это работает…
Этот рецепт сочетает в себе идеи из предыдущих рецептов в этой
главе. Как и в предыдущих рецептах, мы настраиваем потребителя и
производителя Kafka. В этом рецепте используется синхронный
производитель из рецепта «Использование Kafka с Sarama», но вместо
этого можно было бы использовать и асинхронного производителя.
Как только сообщение получено, мы помещаем его в очередь на
входном канале, как мы это делали в рецепте «Goflow для
программирования потока данных». Мы модифицируем компоненты
из этого рецепта, чтобы преобразовать нашу входящую строку в
верхний регистр, а не кодировать ее в Base64. Мы повторно
используем компоненты печати, и итоговая конфигурация сети
аналогична.

Tlgm: @it_boooks

Конечным результатом является то, что все сообщения, полученные
через потребителя Kafka, транспортируются в наш рабочий конвейер
на основе потока для обработки. Это позволяет нам сделать наши
компоненты конвейера модульными и многоразовыми, и мы можем
использовать один и тот же компонент несколько раз в разных
конфигурациях. Точно так же мы будем получать трафик от любого
производителя, который пишет в Kafka, поэтому мы можем объединять
производителей в один поток данных.

Пишем сервер GraphQL на Go
GraphQL — это альтернатива REST, созданная Facebook (http://graphql.
org/). Эта технология позволяет серверу реализовать и опубликовать
схему, после чего клиенты могут запрашивать необходимую им
информацию, вместо того чтобы понимать и использовать различные
конечные точки API.
Для этого рецепта мы создадим схему Graphql, представляющую
колоду игральных карт. Мы выложим одну карту ресурса, которую
можно отфильтровать по масти и достоинству. Кроме того, эта схема
может возвращать все карты в колоде, если аргументы не указаны.

Как это сделать…
Эти шаги охватывают написание и запуск вашего приложения:
В терминале или консольном приложении создайте новый каталог
с именем ~/projects/go-programmingcookbook/chapter12/graphql и перейдите в этот каталог.
Запустите эту команду:
$ go mod init github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter12/graphql
Вы должны увидеть файл с именем go.mod, который содержит
следующее:
module github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter12/graphql
Скопируйте тесты из ~/projects/go-programming-cookbookoriginal/chapter12/graphql или используйте это как

Tlgm: @it_boooks

упражнение для написания собственного кода!
Создайте и перейдите в каталог cards.
Создайте файл с именем card.go со следующим содержимым:
package cards
// Card represents a standard playing
// card
type Card struct {
Value string
Suit string
}
var cards []Card
func init() {
cards = []Card{
{"A", "Spades"}, {"2", "Spades"}, {"3",
"Spades"},
{"4", "Spades"}, {"5", "Spades"}, {"6",
"Spades"},
{"7", "Spades"}, {"8", "Spades"}, {"9",
"Spades"},
{"10", "Spades"}, {"J", "Spades"}, {"Q",
"Spades"},
{"K", "Spades"},
{"A", "Hearts"}, {"2", "Hearts"}, {"3",
"Hearts"},
{"4", "Hearts"}, {"5", "Hearts"}, {"6",
"Hearts"},
{"7", "Hearts"}, {"8", "Hearts"}, {"9",
"Hearts"},
{"10", "Hearts"}, {"J", "Hearts"}, {"Q",
"Hearts"},
{"K", "Hearts"},
{"A", "Clubs"}, {"2", "Clubs"}, {"3",
"Clubs"},
{"4", "Clubs"}, {"5", "Clubs"}, {"6",
"Clubs"},
{"7", "Clubs"}, {"8", "Clubs"}, {"9",
"Clubs"},
{"10", "Clubs"}, {"J", "Clubs"}, {"Q",

Tlgm: @it_boooks

"Clubs"},

{"K", "Clubs"},
{"A", "Diamonds"}, {"2", "Diamonds"},

{"3",

"Diamonds"},
{"4", "Diamonds"}, {"5", "Diamonds"},

{"6",

"Diamonds"},
{"7", "Diamonds"}, {"8", "Diamonds"},

{"9",

"Diamonds"},
{"10", "Diamonds"}, {"J", "Diamonds"},

{"Q",

"Diamonds"},
{"K", "Diamonds"},
}
}
Создайте файл с именем type.go со следующим содержимым:
package cards
import "github.com/graphql-go/graphql"
// CardType returns our card graphql object
func CardType() *graphql.Object {
cardType :=
graphql.NewObject(graphql.ObjectConfig{
Name: "Card",
Description: "A Playing Card",
Fields: graphql.Fields{
"value": &graphql.Field{
Type: graphql.String,
Description: "Ace through King",
Resolve: func(p
graphql.ResolveParams)
(interface{}, error) {
if card, ok := p.Source.
(Card); ok {
return card.Value, nil
}
return nil, nil
},

Tlgm: @it_boooks

},
"suit": &graphql.Field{
Type: graphql.String,
Description: "Hearts, Diamonds,

Clubs, Spades",

Resolve: func(p
graphql.ResolveParams)
(Card); ok {

(interface{}, error) {
if card, ok := p.Source.
return card.Suit, nil
}
return nil, nil
},

},

},

})
return cardType
}
Создайте файл с именем resolve.go со следующим содержимым:
package cards
import (
"strings"
"github.com/graphql-go/graphql"
)
// Resolve handles filtering cards
// by suit and value
func Resolve(p graphql.ResolveParams)
(interface{}, error) {
finalCards := []Card{}
suit, suitOK := p.Args["suit"].(string)
suit = strings.ToLower(suit)
value, valueOK := p.Args["value"].(string)
value = strings.ToLower(value)
for _, card := range cards {
if suitOK && suit !=

Tlgm: @it_boooks

strings.ToLower(card.Suit) {
continue
}
if valueOK && value !=
strings.ToLower(card.Value) {
continue
}
finalCards = append(finalCards, card)
}
return finalCards, nil
}
Создайте файл с именем schema.go со следующим содержимым:
package cards
import "github.com/graphql-go/graphql"
// Setup prepares and returns our card
// schema
func Setup() (graphql.Schema, error) {
cardType := CardType()
// Schema
fields := graphql.Fields{
"cards": &graphql.Field{
Type: graphql.NewList(cardType),
Args: graphql.FieldConfigArgument{
"suit": &graphql.ArgumentConfig{
Description: "Filter cards by
card suit

(hearts, clubs, diamonds,

spades)",
Type: graphql.String,

},
"value": &graphql.ArgumentConfig{
Description: "Filter cards by
card
value (A-K)",
Type: graphql.String,
},
},

Tlgm: @it_boooks

Resolve: Resolve,
}

},

rootQuery := graphql.ObjectConfig{Name:
"RootQuery",
Fields: fields}
schemaConfig := graphql.SchemaConfig{Query:
graphql.NewObject(rootQuery)}
schema, err := graphql.NewSchema(schemaConfig)
return schema, err
}
Вернитесь в каталог graphql.
Создайте новый каталог с именем example и перейдите к нему.
Создайте файл с именем main.go со следующим содержимым:
package main
import (
"encoding/json"
"fmt"
"log"
"github.com/PacktPublishing/
Go-Programming-Cookbook-Second-Edition/
chapter12/graphql/cards"
"github.com/graphql-go/graphql"
)
func main() {
// grab our schema
schema, err := cards.Setup()
if err != nil {
panic(err)
}
// Query
query := `
{
cards(value: "A"){
value

Tlgm: @it_boooks

}`

}

suit

params := graphql.Params{Schema: schema,
RequestString:
query}
r := graphql.Do(params)
if len(r.Errors) > 0 {
log.Fatalf("failed to execute graphql
operation,
errors: %+v", r.Errors)
}
rJSON, err := json.MarshalIndent(r, "", " ")
if err != nil {
panic(err)
}
fmt.Printf("%s \n", rJSON)
}
Выполните go run main.go.
Вы также можете запустить следующую команду:
$ go build
$ ./example
Вы должны увидеть следующий вывод:
$ go run main.go
{
"data": {
"cards": [
{
"suit": "Spades",
"value": "A"
},
{
"suit": "Hearts",
"value": "A"
},
{
"suit": "Clubs",
"value": "A"
},

Tlgm: @it_boooks

{
"suit": "Diamonds",
"value": "A"
}
]
}
}
Протестируйте некоторые дополнительные запросы, например
следующие:
cards(suit: "Spades")
cards(value: "3", suit:"Diamonds")
Файл go.mod может быть обновлен, и теперь файл go.sum должен
присутствовать в каталоге рецептов верхнего уровня.
Если вы скопировали или написали свои собственные тесты,
перейдите на один каталог вверх и запустите go test. Убедитесь,
что все тесты пройдены.

Как это работает…
Файл cards.go определяет объект card и инициализирует базовую
колоду в глобальной переменной с именем cards. Это состояние также
может храниться в долговременном хранилище, например в базе
данных. Затем мы определяем CardType в types.go, что позволяет
graphql преобразовывать объекты карты в ответы. Затем мы
переходим к resolve.go, где определяем, как фильтровать карты по
значению и типу. Эта функция Resolve будет использоваться
окончательной схемой, которая определена в schema.go.
Например, вы должны изменить функцию Resolve в этом рецепте,
чтобы получить данные из базы данных. Наконец, мы загружаем схему
и запускаем к ней запрос. Это небольшая модификация для
подключения нашей схемы к конечной точке REST, но для краткости
этот рецепт просто запускает жестко заданный запрос. Для получения
дополнительной информации о запросах GraphQL посетите
http://graphql.org/learn/queries/.

Tlgm: @it_boooks

13. Бессерверное
программирование
В этой главе основное внимание будет уделено бессерверным
архитектурам и их использованию с языком Go. В бессерверных
архитектурах разработчик не управляет внутренним сервером. Сюда
входят такие сервисы, как Amazon Lambda, Google App Engine и
Firebase. Эти сервисы позволяют быстро развертывать приложения и
хранить данные в Интернете.
Все рецепты в этой главе касаются сторонних сервисов,
выставляющих счета за использование; убедитесь, что вы убираете,
когда закончите их использовать. В противном случае считайте эти
рецепты стартовыми для запуска больших приложений на этих
платформах.
В этой главе мы рассмотрим следующие рецепты:
Go программирование на Lambda с Apex
Бессерверное ведение журналов и метрик Apex
Google App Engine с Go
Работа с Firebase с помощью firebase.google.com/go

Go программирование на Lambda с
Apex
Apex — это инструмент для создания, развертывания и управления
функциями AWS Lambda. Раньше он предоставлял Go shim для
управления функциями Lambda в коде, но теперь это делается с
помощью собственной библиотеки AWS (https://github.com/aws/aws-la
mbda-go). В этом рецепте рассматривается создание функций Go
Lambda и их развертывание с помощью Apex.

Подготовка
Настройте среду в соответствии с этими шагами:

Tlgm: @it_boooks

Загрузите и установите Go 1.12.6 или более позднюю версию в
своей операционной системе с https://golang.org/doc/install.
Установите Apex с http://apex.run/#installation.
Откройте терминал или консольное приложение, создайте и
перейдите в каталог проекта, например ~/projects/goprogramming-cookbook. Весь код, который мы рассмотрим в этом
рецепте, будет запускаться и модифицироваться из этого каталога.
Скопируйте последний код в ~/projects/go-programmingcookbook-original. Здесь у вас есть возможность работать из
этого каталога, а не вводить примеры вручную:
$ git clone git@github.com:PacktPublishing/GoProgramming-Cookbook-Second-Edition.git go-programmingcookbook-original

Как это сделать…
Эти шаги охватывают написание и запуск вашего приложения:
В терминале или консольном приложении создайте новый каталог
с именем ~/projects/go-programmingcookbook/chapter13/lambda и перейдите в него.
Выполните следующую команду:
$ go mod init github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter13/lambda
Вы должны увидеть файл с именем go.mod, содержащий
следующее содержимое:
module github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter13/lambda
Создайте учетную запись Amazon и роль IAM, которая может
редактировать функции Lambda, что можно сделать на странице ht
tps://aws.amazon.com/lambda/.
Создайте файл с именем ~/.aws/credentials со следующим
содержимым, скопировав свои учетные данные из того, что вы
настроили в консоли Amazon:
[default]
aws_access_key_id = xxxxxxxx

Tlgm: @it_boooks

aws_secret_access_key = xxxxxxxxxxxxxxxxxxxxxxxx
Создайте переменную среды для хранения нужного региона:
export AWS_REGION=us-west-2
Запустите команду apex init и следуйте инструкциям на экране:
$ apex init
Enter the name of your project. It should be machinefriendly, as this is used to prefix your functions in
Lambda.
Project name: go-cookbook
Enter an optional description of your project.
Project description: Demonstrating Apex with the Go
Cookbook
[+]
[+]
[+]
[+]
[+]

creating IAM go-cookbook_lambda_function role
creating IAM go-cookbook_lambda_logs policy
attaching policy to lambda_function role.
creating ./project.json
creating ./functions

Setup complete, deploy those functions!
$ apex deploy
Удалите каталог lambda/functions/hello.
Создайте новый файл lambda/functions/greeter1/main.go со
следующим содержимым:
package main
import (
"context"
"fmt"
)

"github.com/aws/aws-lambda-go/lambda"

Tlgm: @it_boooks

// Message is the input to the function and
// includes a Name
type Message struct {
Name string `json:"name"`
}
// Response is sent back and contains a greeting
// string
type Response struct {
Greeting string `json:"greeting"`
}
// HandleRequest will be called when the lambda function
is invoked
// it takes a Message and returns a Response that
contains a greeting
func HandleRequest(ctx context.Context, m Message)
(Response, error) {
return Response{Greeting: fmt.Sprintf("Hello, %s",
m.Name)}, nil
}
func main() {
lambda.Start(HandleRequest)
}
Создайте новый файл lambda/functions/greeter/main.go со
следующим содержимым:
package main
import (
"context"
"fmt"
)

"github.com/aws/aws-lambda-go/lambda"

// Message is the input to the function and
// includes a FirstName and LastName
type Message struct {
FirstName string `json:"first_name"`
LastName string `json:"last_name"`

Tlgm: @it_boooks

}
// Response is sent back and contains a greeting
// string
type Response struct {
Greeting string `json:"greeting"`
}
// HandleRequest will be called when the lambda function
is invoked
// it takes a Message and returns a Response that
contains a greeting
// this greeting contains the first and last name
specified
func HandleRequest(ctx context.Context, m Message)
(Response, error) {
return Response{Greeting: fmt.Sprintf("Hello, %s %s",
m.FirstName, m.LastName)}, nil
}
func main() {
lambda.Start(HandleRequest)
}
Разверните их:
$ apex deploy
• creating function env= function=greeter2
• creating function env= function=greeter1
• created alias current env= function=greeter2 version=4
• function created env= function=greeter2 name=gocookbook_greeter2 version=1
• created alias current env= function=greeter1 version=5
• function created env= function=greeter1 name=gocookbook_greeter1 version=1
Вызовите недавно развернутые функции:
$ echo '{"name": "Reader"}' | apex invoke greeter1
{"greeting":"Hello, Reader"}
$ echo '{"first_name": "Go", "last_name": "Coders"}' |
apex invoke greeter2 {"greeting":"Hello, Go Coders"}

Tlgm: @it_boooks

Взгляните на журналы:
$ apex logs greeter2
apex logs greeter2
/aws/lambda/go-cookbook_greeter2 START RequestId:
7c0f9129-3830-11e7-8755-75aeb52a51b9 Version: 1
/aws/lambda/go-cookbook_greeter2 END RequestId: 7c0f91293830-11e7-8755-75aeb52a51b9
/aws/lambda/go-cookbook_greeter2 REPORT RequestId:
7c0f9129-3830-11e7-8755-75aeb52a51b9 Duration: 93.84 ms
Billed Duration: 100 ms
Memory Size: 128 MB Max Memory Used: 19 MB
Очистите развернутые службы:
$ apex delete
The following will be deleted:
- greeter1
- greeter2
Are you sure? (yes/no) yes
• deleting env= function=greeter
• function deleted env= function=greeter

Как это работает…
AWS Lambda позволяет легко запускать функции по запросу без
обслуживания сервера. Apex предоставляет средства для
развертывания, управления версиями и тестирования функций по мере
их отправки в Lambda.
Библиотека Go (https://github.com/aws/aws-lambda-go) обеспечивает
нативную компиляцию Go в Lambda и позволяет нам развертывать код
Go как функции Lambda. Это достигается путем определения
обработчика, обработки полезной нагрузки входящего запроса и
возврата ответа. В настоящее время функции, которые вы определяете,
должны соответствовать следующим правилам:
Обработчик должен быть функцией.
Обработчик может принимать от нуля до двух аргументов.
При наличии двух аргументов первый аргумент должен
удовлетворять интерфейсу context.Context.

Tlgm: @it_boooks

Обработчик может вернуть от нуля до двух аргументов.
Если есть два возвращаемых значения, второй аргумент должен
быть ошибкой.
Если есть одно возвращаемое значение, это должна быть ошибка.
В этом рецепте мы определили две функции приветствия: одну,
которая принимает полное имя, и другую, в которой мы разделяем имя
на имя и фамилию. Если бы мы изменили одну функцию (greeter)
вместо создания двух, Apex вместо этого развернул бы новую версию
и вызвал бы ее в v2, а не в v1 во всех предыдущих примерах. Также
можно было бы откатиться и с apex rollback greeter.

Бессерверное ведение журналов и
метрик Apex
При работе с бессерверными функциями, такими как Lambda, полезно
иметь переносимые структурированные журналы. Кроме того, вы
можете комбинировать более ранние рецепты, связанные с ведением
журнала, с этим рецептом. Рецепты, которые мы рассмотрели в Главе 4
«Обработка ошибок в Go», не менее актуальны. Поскольку мы
используем Apex для управления нашими функциями Lambda, мы
решили использовать регистратор Apex для этого рецепта. Мы также
будем полагаться на метрики, предоставляемые Apex, а также на
консоль AWS. В более ранних рецептах рассматривались более
сложные примеры ведения журналов и метрик, и они все еще
применимы — регистратор Apex можно легко настроить для
агрегирования журналов с помощью, например, Amazon Kinesis или
Elasticsearch.

Подготовка
Обратитесь к разделу «Подготовка» рецепта «Go программирование на
Lambda с Apex» в этой главе.

Как это сделать…
Эти шаги охватывают написание и запуск вашего приложения:

Tlgm: @it_boooks

В терминале или консольном приложении создайте новый каталог
с именем ~/projects/go-programmingcookbook/chapter13/logging и перейдите к нему.
Выполните следующую команду:
$ go mod init github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter13/logging
Вы должны увидеть файл с именем go.mod, содержащий
следующее содержимое:
module github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter13/logging
Создайте учетную запись Amazon и роль IAM, которая может
редактировать функции Lambda, что можно сделать по адресу http
s://aws.amazon.com/lambda/.
Создайте файл ~/.aws/credentials со следующим содержимым,
скопировав свои учетные данные из того, что вы настроили в
консоли Amazon:
[default]
aws_access_key_id = xxxxxxxx
aws_secret_access_key = xxxxxxxxxxxxxxxxxxxxxxxx
Создайте переменную среды для хранения нужного региона:
export AWS_REGION=us-west-2
Запустите команду apex init и следуйте инструкциям на экране:
$ apex init
Enter the name of your project. It should be machinefriendly, as this is used to prefix your functions in
Lambda.
Project name: logging
Enter an optional description of your project.
Project description: An example of apex logging and
metrics

Tlgm: @it_boooks

[+]
[+]
[+]
[+]
[+]

creating IAM logging_lambda_function role
creating IAM logging_lambda_logs policy
attaching policy to lambda_function role.
creating ./project.json
creating ./functions

Setup complete, deploy those functions!
$ apex deploy
Удалите каталог lambda/functions/hello.
Создайте новый файл lambda/functions/secret/main.go со
следующим содержимым:
package main
import (
"context"
"os"
"github.com/apex/log"
"github.com/apex/log/handlers/text"
"github.com/aws/aws-lambda-go/lambda"
)
// Input takes in a secret
type Input struct {
Secret string `json:"secret"`
}
// HandleRequest will be called when the Lambda function
is invoked
// it takes an input and checks if it matches our super
secret value
func HandleRequest(ctx context.Context, input Input)
(string, error) {
log.SetHandler(text.New(os.Stderr))
log.WithField("secret", input.Secret).Info("secret
guessed")
if input.Secret == "klaatu barada nikto" {

Tlgm: @it_boooks

}

return "secret guessed!", nil
}
return "try again", nil

func main() {
lambda.Start(HandleRequest)
}
Разверните его в указанном вами регионе:
$ apex deploy
• creating function env= function=secret
• created alias current env= function=secret version=1
• function created env= function=secret
name=logging_secret version=1
Чтобы вызвать его, выполните следующую команду:
$ echo '{"secret": "open sesame"}' | apex invoke secret
"try again"
$ echo '{"secret": "klaatu barada nikto"}' | apex invoke
secret
"secret guessed!"
Проверьте журналы:
$ apex logs secret
/aws/lambda/logging_secret START RequestId: cfa6f6553834-11e7-b99d-89998a7f39dd Version: 1
/aws/lambda/logging_secret INFO[0000] secret guessed
secret=open sesame
/aws/lambda/logging_secret END RequestId: cfa6f655-383411e7-b99d-89998a7f39dd
/aws/lambda/logging_secret REPORT RequestId: cfa6f6553834-11e7-b99d-89998a7f39dd Duration: 52.23 ms Billed
Duration: 100 ms Memory Size: 128 MB Max Memory Used: 19
MB
/aws/lambda/logging_secret START RequestId: d74ea6883834-11e7-aa4e-d592c1fbc35f Version: 1
/aws/lambda/logging_secret INFO[0012] secret guessed
secret=klaatu barada nikto
/aws/lambda/logging_secret END RequestId: d74ea688-3834-

Tlgm: @it_boooks

11e7-aa4e-d592c1fbc35f
/aws/lambda/logging_secret REPORT RequestId: d74ea6883834-11e7-aa4e-d592c1fbc35f Duration: 7.43 ms Billed
Duration: 100 ms
Memory Size: 128 MB Max Memory Used: 19 MB
Проверьте свои показатели:
$ apex metrics secret
secret
total cost: $0.00
invocations: 0 ($0.00)
duration: 0s ($0.00)
throttles: 0
errors: 0
memory: 128
Очистите развернутые службы:
$ apex delete
Are you sure? (yes/no) yes
• deleting env= function=secret
• function deleted env= function=secret

Как это работает…
В этом рецепте мы создали новую лямбда-функцию под названием
secret, которая будет отвечать, угадали ли вы секретную фразу или нет.
Функция анализирует входящий запрос JSON, выполняет некоторую
регистрацию с помощью Stderr и возвращает ответ.
После использования этой функции несколько раз мы видим, что наши
журналы видны с помощью команды apex logs. Эту команду можно
запустить для одной функции Lambda или для всех наших
управляемых функций. Это особенно полезно, если вы объединяете
команды Apex в цепочку и хотите просматривать журналы многих
служб.
Кроме того, мы показали, как использовать команду apex metrics для
сбора общих показателей вашего приложения, включая стоимость и
вызовы. Вы также можете увидеть много этой информации

Tlgm: @it_boooks

непосредственно в консоли AWS в разделе Lambda. Как и в других
рецептах, в конце мы попытались убрать за собой.

Google App Engine с Go
App Engine – это служба Google, которая упрощает развертывание вебприложений. Эти приложения имеют доступ к облачному хранилищу и
различным другим API Google. Общая идея заключается в том, что
App Engine будет легко масштабироваться с нагрузкой и упростит
любое управление операциями, связанными с размещением
приложения. В этом рецепте показано, как создать и при
необходимости развернуть базовое приложение App Engine. Этот
рецепт не будет вдаваться в подробности настройки учетной записи
Google Cloud, настройки выставления счетов или особенностей
очистки вашего экземпляра. Как минимум, для работы этого рецепта
требуется доступ к Google Cloud Datastore (https://cloud.google.com/data
store/docs/concepts/overview).

Подготовка
Настройте среду в соответствии с этими шагами:
Загрузите и установите Go 1.11.1 или более позднюю версию в
своей операционной системе с https://golang.org/doc/install.
Загрузите Google Cloud SDK со страницы https://cloud.google.com/
appengine/docs/flexible/go/quickstart.
Создайте приложение, позволяющее выполнять доступ к
хранилищу данных, и запишите имя приложения. Для этого
рецепта мы будем использовать go-cookbook.
Установите компонент движка приложения Go gcloud
components install app-engine-go.
Откройте терминал или консольное приложение, создайте и
перейдите в каталог проекта, например ~/projects/goprogramming-cookbook. Весь код, который мы рассмотрим в этом
рецепте, будет запускаться и модифицироваться из этого каталога.
Скопируйте последний код в ~/projects/go-programmingcookbook-original. Здесь у вас есть возможность работать из
этого каталога, а не вводить примеры вручную:

Tlgm: @it_boooks

$ git clone git@github.com:PacktPublishing/GoProgramming-Cookbook-Second-Edition.git go-programmingcookbook-original

Как это сделать…
Эти шаги охватывают написание и запуск вашего приложения:
В терминале или консольном приложении создайте новый каталог
с именем ~/projects/go-programmingcookbook/chapter13/appengine и перейдите в него.
Выполните следующую команду:
$ go mod init github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter13/appengine
Вы должны увидеть файл с именем go.mod, содержащий
следующее содержимое:
module github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter13/appengine
Создайте файл с именем app.yml со следующим содержимым,
заменив go-cookbook на имя приложения, которое вы создали в
разделе «Подготовка»:
runtime: go112
manual_scaling:
instances: 1
#[START env_variables]
env_variables:
GCLOUD_DATASET_ID: go-cookbook
#[END env_variables]
Создайте файл с именем message.go со следующим содержимым:
package main
import (
"context"
"time"

Tlgm: @it_boooks

)

"cloud.google.com/go/datastore"

// Message is the object we store
type Message struct {
Timestamp time.Time
Message string
}
func (c *Controller) storeMessage(ctx
context.Context, message
string) error {
m := &amp;amp;Message{
Timestamp: time.Now(),
Message: message,
}
k := datastore.IncompleteKey("Message", nil)
_, err := c.store.Put(ctx, k, m)
return err
}
func (c *Controller) queryMessages(ctx
context.Context, limit
int) ([]*Message, error) {
q := datastore.NewQuery("Message").
Order("-Timestamp").
Limit(limit)
messages := make([]*Message, 0)
_, err := c.store.GetAll(ctx, q,
&amp;amp;messages)
return messages, err
}
Создайте файл с именем controller.go со следующим
содержимым:
package main
import (
"context"
"fmt"

Tlgm: @it_boooks

"log"
"net/http"
)

"cloud.google.com/go/datastore"

// Controller holds our storage and other
// state
type Controller struct {
store *datastore.Client
}
func (c *Controller) handle(w http.ResponseWriter,
r

*http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "invalid method",
http.StatusMethodNotAllowed)
return
}
ctx := context.Background()
// store the new message
r.ParseForm()
if message := r.FormValue("message"); message

!= "" {
err != nil {

if err := c.storeMessage(ctx, message);
log.Printf("could not store message:

%v", err)

}

http.Error(w, "could not store
message",
http.StatusInternalServerError)
return

}
// get the current messages and display them
fmt.Fprintln(w, "Messages:")
messages, err := c.queryMessages(ctx, 10)
if err != nil {
log.Printf("could not get messages: %v",

Tlgm: @it_boooks

err)

}

http.Error(w, "could not get messages",
http.StatusInternalServerError)
return

for _, message := range messages {
fmt.Fprintln(w, message.Message)
}
}
Создайте файл с именем main.go со следующим содержимым:
package main
import (
"log"
"net/http"
"os"
"cloud.google.com/go/datastore"
"golang.org/x/net/context"
"google.golang.org/appengine"
)
func main() {
ctx := context.Background()
log.SetOutput(os.Stderr)
// Set this in app.yaml when running in
production.
projectID := os.Getenv("GCLOUD_DATASET_ID")
datastoreClient, err :=
datastore.NewClient(ctx, projectID)
if err != nil {
log.Fatal(err)
}
c := Controller{datastoreClient}
http.HandleFunc("/", c.handle)

Tlgm: @it_boooks

port := os.Getenv("PORT")
if port == "" {
port = "8080"
log.Printf("Defaulting to port %s", port)
}
log.Printf("Listening on port %s", port)
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", port),
nil))
}
Запустите команду gcloud config set project go-cookbook,
где go-cookbook — это проект, который вы создали в разделе
«Подготовка».
Запустите команду cloud auth application-default login и
следуйте инструкциям.
Запустите команду export PORT=8080.
Запустите команду export GCLOUD_DATASET_ID=go-cookbook, где
go-cookbook — это проект, который вы создали в разделе
«Подготовка».
Запустите команду go build.
Запустите команду ./appengine.
Перейдите по адресу http://localhost:8080/?message=hello%20there.
Попробуйте отправить еще несколько сообщений (?
message=other).
При необходимости разверните приложение в своем экземпляре с
помощью gcloud app deploy.
NПерейдите к развернутому приложению с помощью gcloud app
browse.
При желании очистите свой экземпляр appengine и хранилище
данных по следующим URL-адресам:
https://console.cloud.google.com/datastore
https://console.cloud.google.com/appengine
Файл go.mod может быть обновлен, и теперь файл go.sum должен
присутствовать в каталоге рецептов верхнего уровня.
Если вы скопировали или написали свои собственные тесты,
запустите команду go test. Убедитесь, что все тесты пройдены.

Tlgm: @it_boooks

Как это работает…
Как только облачный SDK настроен так, чтобы указывать на ваше
приложение, и прошел проверку подлинности, инструмент GCloud
позволяет быстро развертывать и настраивать, позволяя локальным
приложениям получать доступ к службам Google.
После аутентификации и установки порта мы запускаем приложение
на localhost и можем приступать к работе с кодом. Приложение
определяет объект сообщения, который можно сохранить и извлечь из
хранилища данных. Это демонстрирует, как вы можете изолировать
такой код. Вы также можете использовать интерфейс хранилища/базы
данных, как показано в предыдущих главах.
Затем мы настраиваем обработчик, который пытается вставить
сообщение в хранилище данных, а затем извлекает все сообщения,
отображая их в браузере. Это создает что-то похожее на простую
гостевую книгу. Вы можете заметить, что сообщение не всегда
появляется сразу. Если вы перемещаетесь без параметра сообщения
или отправляете другое сообщение, оно должно появиться при
перезагрузке.
Наконец, убедитесь, что вы очистили экземпляры, если вы их больше
не используете.

Работа с Firebase с помощью
rebase.google.com/go
Firebase — еще один сервис Google Cloud, который создает
масштабируемую, простую в управлении базу данных, которая может
поддерживать аутентификацию и особенно хорошо работает с
мобильными приложениями. В этом рецепте мы будем использовать
последнюю версию Firestore в качестве базы данных. Служба Firebase
предоставляет значительно больше, чем то, что будет описано в этом
рецепте, но мы просто рассмотрим хранение и извлечение данных. Мы
также рассмотрим, как настроить аутентификацию для вашего
приложения и обернуть клиент Firebase нашим собственным
настраиваемым клиентом.

Tlgm: @it_boooks

Подготовка
Настройте среду в соответствии с этими шагами:
Загрузите и установите Go 1.11.1 или более позднюю версию в
своей операционной системе с https://golang.org/doc/install..
Создайте учетную запись Firebase, проект и базу данных на
странице https://console.firebase.google.com/.
Этот рецепт работает в тестовом режиме, который
по умолчанию не является безопасным.
Создайте токен администратора службы, перейдя по адресу https://
console.firebase.google.com/project/go-cookbook/settings/serviceacco
unts/adminsdk. Здесь go-cookbook заменяется названием вашего
проекта.
Переместите загруженный токен в /tmp/service_account.json.
Откройте терминал или консольное приложение, создайте и
перейдите в каталог проекта, например ~/projects/goprogramming-cookbook. Весь код, который мы рассмотрим в этом
рецепте, будет запускаться и модифицироваться из этого каталога.
Скопируйте последний код в ~/projects/go-programmingcookbook-original. Здесь у вас есть возможность работать из
этого каталога, а не вводить примеры вручную:
$ git clone git@github.com:PacktPublishing/GoProgramming-Cookbook-Second-Edition.git go-programmingcookbook-original

Как это сделать…
Эти шаги охватывают написание и запуск вашего приложения:
В терминале или консольном приложении создайте новый каталог
с именем ~/projects/go-programmingcookbook/chapter13/firebase и перейдите в него.
Выполните следующую команду:

Tlgm: @it_boooks

$ go mod init github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter13/firebase
Вы должны увидеть файл с именем go.mod, содержащий
следующее содержимое:
module github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter13/firebase
Создайте файл client.go со следующим содержимым:
package firebase
import (
"context"
"cloud.google.com/go/firestore"
"github.com/pkg/errors"
)
// Client Interface for mocking
type Client interface {
Get(ctx context.Context, key string) (interface{},
error)
Set(ctx context.Context, key string, value interface{})
error
Close() error
}
// firestore.Client implements Close()
// we create Get and Set
type firebaseClient struct {
*firestore.Client
collection string
}
func (f *firebaseClient) Get(ctx context.Context, key
string) (interface{}, error) {
data, err :=
f.Collection(f.collection).Doc(key).Get(ctx)
if err != nil {
return nil, errors.Wrap(err, "get failed")
}
return data.Data(), nil

Tlgm: @it_boooks

}
func (f *firebaseClient) Set(ctx context.Context, key
string, value interface{}) error {
set := make(map[string]interface{})
set[key] = value
_, err := f.Collection(f.collection).Doc(key).Set(ctx,
set)
return errors.Wrap(err, "set failed")
}
Создайте файл с именем auth.go со следующим содержимым:
package firebase
import (
"context"
firebase "firebase.google.com/go"
"github.com/pkg/errors"
"google.golang.org/api/option"
)
// Authenticate grabs oauth scopes using a generated
// service_account.json file from
// https://console.firebase.google.com/project/gocookbook/settings/serviceaccounts/adminsdk
func Authenticate(ctx context.Context, collection string)
(Client, error) {
opt :=
option.WithCredentialsFile("/tmp/service_account.json")
app, err := firebase.NewApp(ctx, nil, opt)
if err != nil {
return nil, errors.Wrap(err, "error initializing
app")
}
client, err := app.Firestore(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to intialize
filestore")
}

Tlgm: @it_boooks

return &amp;amp;firebaseClient{Client: client,
collection: collection}, nil
}
Создайте новый каталог с именем example и перейдите к нему.
Создайте файл с именем main.go со следующим содержимым:
package main
import (
"context"
"fmt"
"log"
"github.com/PacktPublishing/Go-Programming-CookbookSecond-Edition/chapter13/firebase"
)
func main() {
ctx := context.Background()
c, err := firebase.Authenticate(ctx, "collection")
if err != nil {
log.Fatalf("error initializing client: %v", err)
}
defer c.Close()
if err := c.Set(ctx, "key", []string{"val1", "val2"});
err != nil {
log.Fatalf(err.Error())
}
res, err := c.Get(ctx, "key")
if err != nil {
log.Fatalf(err.Error())
}
fmt.Println(res)
if err := c.Set(ctx, "key2", []string{"val3", "val4"});
err != nil {
log.Fatalf(err.Error())
}
res, err = c.Get(ctx, "key2")

Tlgm: @it_boooks

if err != nil {
log.Fatalf(err.Error())
}
fmt.Println(res)

}
Выполните go run main.go.
Вы также можете запустить go build ./example. Вы должны
увидеть следующий вывод:
$ go run main.go
[val1 val2]
[val3 val4]
Файл go.mod может быть обновлен, и теперь файл go.sum должен
присутствовать в каталоге рецептов верхнего уровня.
Если вы скопировали или написали свои собственные тесты,
перейдите на один каталог вверх и запустите go test. Убедитесь,
что все тесты пройдены.

Как это работает…
Firebase предоставляет удобные функции, позволяющие войти в
систему с помощью файла учетных данных. После того, как мы вошли
в систему, мы можем сохранить любой структурированный объект,
похожий
на
карту.
В
данном
случае
мы
храним
map[string]interface{}. Эти данные доступны ряду клиентов, в том
числе в Интернете и через мобильные устройства.
Клиентский код заключает все операции в интерфейс для простоты
тестирования. Это распространенный шаблон при написании
клиентского кода, который также используется в других рецептах. В
нашем случае мы создаем функцию Get and Set, которая сохраняет и
извлекает значение по ключу. Мы также выставляем Close(), чтобы
код, использующий клиента, мог отложить close() и очистить наше
соединение в конце.

Tlgm: @it_boooks

14. Улучшения
производительности, советы
и рекомендации
В этой главе мы сосредоточимся на оптимизации приложения и
обнаружении узких мест. Вот несколько советов и приемов, которые
можно сразу же использовать в существующих приложениях. Многие
из этих рецептов необходимы, если вам или вашей организации
требуются полностью воспроизводимые сборки. Они также полезны,
когда вы хотите оценить производительность приложения. Последний
рецепт фокусируется на увеличении скорости HTTP; однако всегда
важно помнить, что мир Интернета движется быстро, и важно
освежать в памяти передовой опыт. Например, если вам требуется
HTTP/2, он доступен с помощью встроенного пакета Go net/http,
начиная с версии 1.6.
В этой главе мы рассмотрим следующие рецепты:
Использование инструмента pprof
Бенчмаркинг и поиск узких мест
Распределение памяти и управление кучей
Использование fasthttprouter и fasthttp

Технические требования
Чтобы продолжить выполнение всех рецептов в этой главе, настройте
свою среду в соответствии со следующими шагами:
Загрузите и установите Go 1.12.6 или более позднюю версию в
своей операционной системе с https://golang.org/doc/install.
Откройте терминал или консольное приложение, создайте и
перейдите в каталог проекта, например ~/projects/goprogramming-cookbook. Весь код, который мы рассмотрим в этом
рецепте, будет запускаться и модифицироваться из этого каталога.
Скопируйте последний код в ~/projects/go-programmingcookbook-original. Здесь у вас есть возможность работать из

Tlgm: @it_boooks

этого каталога, а не вводить примеры вручную:
$ git clone git@github.com:PacktPublishing/GoProgramming-Cookbook-Second-Edition.git go-programmingcookbook-original
При желании установите Graphviz с http://www.graphviz.org/Home.
php.

Использование инструмента pprof
Инструмент pprof позволяет приложениям Go собирать и
экспортировать данные профилирования во время выполнения. Он
также предоставляет веб-перехватчики для доступа к инструменту из
веб-интерфейса. Этот рецепт создаст базовое приложение, которое
сверяет пароль, хешированный с помощью bcrypt, с паролем в виде
открытого текста, а затем профилирует приложение.
Возможно, вы ожидали, что инструмент pprof будет описан в Главе 11
«Распределенные системы» с другими показателями и рецептами
мониторинга. Вместо этого он был помещен в эту главу, потому что он
будет использоваться для анализа и улучшения программы почти так
же, как может использоваться бенчмаркинг. Поэтому в этом рецепте
основное внимание будет уделено pprof для анализа и улучшения
использования памяти приложением.

Как это сделать…
Эти шаги охватывают написание и запуск вашего приложения:
В терминале или консольном приложении создайте новый каталог
с именем ~/projects/go-programmingcookbook/chapter14/pprof и перейдите в этот каталог.
Запустите эту команду:
$ go mod init github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter14/pprof
Вы должны увидеть файл с именем go.mod, который содержит
следующее:

Tlgm: @it_boooks

module github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter14/pprof
Скопируйте тесты из ~/projects/go-programming-cookbookoriginal/chapter14/pprof или используйте это как упражнение
для написания собственного кода!
Создайте каталог с именем crypto и перейдите к нему.
Создайте файл с именем handler.go со следующим содержимым:
package crypto
import (
"net/http"
)

"golang.org/x/crypto/bcrypt"
// GuessHandler checks if ?message=password
func GuessHandler(w http.ResponseWriter, r
*http.Request) {
if err := r.ParseForm(); err != nil{
// if we can't parse the form
// we'll assume it is malformed
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("error reading guess"))
return
}
msg := r.FormValue("message")
// "password"
real :=
[]byte("$2a$10$2ovnPWuIjMx2S0HvCxP/mutzdsGhyt8rq/
JqnJg/6OyC3B0APMGlK")
if err :=
bcrypt.CompareHashAndPassword(real, []byte(msg));
err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("try again"))
return
}

Tlgm: @it_boooks

w.WriteHeader(http.StatusOK)
w.Write([]byte("you got it"))
return
}
Перейдите вверх по каталогу.
Создайте новый каталог с именем example и перейдите к нему.
Создайте файл с именем main.go со следующим содержимым:
package main
import (
"fmt"
"log"
"net/http"
_ "net/http/pprof"
"github.com/PacktPublishing/
Go-Programming-Cookbook-Second-Edition/
chapter14/pprof/crypto"
)
func main() {
http.HandleFunc("/guess", crypto.GuessHandler)
fmt.Println("server started at
localhost:8080")
log.Panic(http.ListenAndServe("localhost:8080", nil))
}
Выполните go run main.go.
Вы также можете запустить следующую команду:
$ go build
$ ./example
Вы также можете запустить следующие команды:
$ go run main.go
server started at localhost:8080
В отдельном терминале выполните следующее:

Tlgm: @it_boooks

$ go tool pprof http://localhost:8080/debug/pprof/profile
Это запустит 30-секундный таймер.
Запустите несколько команд curl во время работы pprof:
$ curl "http://localhost:8080/guess?message=test"
try again
$curl "http://localhost:8080/guess?message=password"
you got it
.
.
.
.
$curl "http://localhost:8080/guess?message=password"
you got it
Вернитесь к команде pprof и дождитесь ее завершения.
Запустите команду top10 из командной строки pprof:
(pprof) top 10
930ms of 930ms total ( 100%)
Showing top 10 nodes out of 15 (cum >= 930ms)
flat flat% sum% cum cum%
870ms 93.55% 93.55% 870ms 93.55%
golang.org/x/crypto/blowfish.encryptBlock
30ms 3.23% 96.77% 900ms 96.77%
golang.org/x/crypto/blowfish.ExpandKey
30ms 3.23% 100% 30ms 3.23% runtime.memclrNoHeapPointers
0 0% 100% 930ms 100% github.com/agtorre/gocookbook/chapter13/pprof/crypto.GuessHandler
0 0% 100% 930ms 100%
golang.org/x/crypto/bcrypt.CompareHashAndPassword
0 0% 100% 30ms 3.23%
golang.org/x/crypto/bcrypt.base64Encode
0 0% 100% 930ms 100% golang.org/x/crypto/bcrypt.bcrypt
0 0% 100% 900ms 96.77%
golang.org/x/crypto/bcrypt.expensiveBlowfishSetup
0 0% 100% 930ms 100% net/http.(*ServeMux).ServeHTTP
0 0% 100% 930ms 100% net/http.(*conn).serve

Tlgm: @it_boooks

Если вы установили Graphviz или поддерживаемый браузер,
запустите команду web из командной строки pprof. Вы должны
увидеть что-то вроде этого с гораздо более длинной цепочкой
красных прямоугольников с правой стороны:

Файл go.mod может быть обновлен, и теперь файл go.sum должен
присутствовать в каталоге рецептов верхнего уровня.
Если вы скопировали или написали свои собственные тесты,
перейдите на один каталог вверх и запустите go test. Убедитесь,
что все тесты пройдены.

Как это работает…
Инструмент pprof предоставляет много информации о вашем
приложении во время выполнения. Использование пакета net/pprof
обычно наиболее просто для настройки — все, что требуется, — это
прослушивание порта и выполнение импорта.
В нашем случае мы написали обработчик, использующий очень
ресурсоемкое приложение (bcrypt), чтобы продемонстрировать, как
они появляются при профилировании с помощью pprof. Это позволит
быстро изолировать фрагменты кода, создающие узкие места в вашем
приложении.
Мы решили собрать общий профиль, который заставляет pprof
опрашивать конечную точку нашего приложения в течение 30 секунд.

Tlgm: @it_boooks

Затем мы сгенерировали трафик для конечной точки, чтобы получить
результаты. Это может быть полезно, когда вы пытаетесь проверить
один обработчик или ветвь кода.
Наконец, мы рассмотрели 10 самых популярных функций с точки
зрения использования ЦП. Также можно просмотреть управление
памятью/кучей
с
помощью
команды
pprof
http://localhost:8080/debug/pprof/heap. Команду web в консоли
pprof можно использовать для просмотра визуализации вашего
профиля ЦП/памяти и помогает выделить более активный код.

Бенчмаркинг и поиск узких мест
Еще один метод определения медленных частей кода — использование
тестов. Тесты можно использовать для тестирования функций на
среднюю производительность, а также можно запускать тесты
параллельно.Это может быть полезно при сравнении функций или
микрооптимизации определенного кода, особенно для того, чтобы
увидеть, как может работать реализация функции при ее
одновременном использовании. Для этого рецепта мы создадим две
структуры, каждая из которых реализует атомарный счетчик. Первый
будет использовать пакет sync, а другой — sync/atomic. Затем мы
сравним оба решения.

Как это сделать…
Эти шаги охватывают написание и запуск вашего приложения:
В терминале или консольном приложении создайте новый каталог
с именем ~/projects/go-programmingcookbook/chapter14/bench и перейдите в этот каталог.
Запустите эту команду:
$ go mod init github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter14/bench
Вы должны увидеть файл с именем go.mod, который содержит
следующее:
module github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter14/bench

Tlgm: @it_boooks

Скопируйте тесты из ~/projects/go-programming-cookbookoriginal/chapter14/bench или используйте это как упражнение
для написания собственного кода!
Обратите внимание, что скопированные тесты также
включают тесты, написанные позже в этом рецепте.
Создайте файл с именем lock.go со следующим содержимым:
package bench
import "sync"
// Counter uses a sync.RWMutex to safely
// modify a value
type Counter struct {
value int64
mu *sync.RWMutex
}
// Add increments the counter
func (c *Counter) Add(amount int64) {
c.mu.Lock()
c.value += amount
c.mu.Unlock()
}
// Read returns the current counter amount
func (c *Counter) Read() int64 {
c.mu.RLock()
defer c.mu.RUnlock()
return c.value
}
Создайте файл с именем atomic.go со следующим содержимым:
package bench
import "sync/atomic"
// AtomicCounter implements an atmoic lock

Tlgm: @it_boooks

// using the atomic package
type AtomicCounter struct {
value int64
}
// Add increments the counter
func (c *AtomicCounter) Add(amount int64) {
atomic.AddInt64(&c.value, amount)
}
// Read returns the current counter amount
func (c *AtomicCounter) Read() int64 {
var result int64
result = atomic.LoadInt64(&c.value)
return result
}
Создайте файл с именем lock_test.go со следующим
содержимым:
package bench
import "testing"
func BenchmarkCounterAdd(b *testing.B) {
c := Counter{0, &sync.RWMutex{}}
for n := 0; n < b.N; n++ {
c.Add(1)
}
}
func BenchmarkCounterRead(b *testing.B) {
c := Counter{0, &sync.RWMutex{}}
for n := 0; n < b.N; n++ {
c.Read()
}
}
func BenchmarkCounterAddRead(b *testing.B) {
c := Counter{0, &sync.RWMutex{}}
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
c.Add(1)

Tlgm: @it_boooks

})

}

c.Read()

}
Создайте файл с именем atomic_test.go со следующим
содержимым:
package bench
import "testing"
func BenchmarkAtomicCounterAdd(b *testing.B) {
c := AtomicCounter{0}
for n := 0; n < b.N; n++ {
c.Add(1)
}
}
func BenchmarkAtomicCounterRead(b *testing.B) {
c := AtomicCounter{0}
for n := 0; n < b.N; n++ {
c.Read()
}
}
func BenchmarkAtomicCounterAddRead(b *testing.B) {
c := AtomicCounter{0}
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
c.Add(1)
c.Read()
}
})
}
Запустите go test-bench . команду, и вы увидите следующий
вывод:
$ go test -bench .
BenchmarkAtomicCounterAdd-4 200000000 8.38 ns/op
BenchmarkAtomicCounterRead-4 1000000000 2.09 ns/op
BenchmarkAtomicCounterAddRead-4 50000000 24.5 ns/op
BenchmarkCounterAdd-4 50000000 34.8 ns/op

Tlgm: @it_boooks

BenchmarkCounterRead-4 20000000 66.0 ns/op
BenchmarkCounterAddRead-4 10000000 146 ns/op
PASS
ok github.com/PacktPublishing/Go-Programming-CookbookSecondEdition/chapter14/bench 10.919s
Если вы скопировали или написали свои собственные тесты,
перейдите на один каталог вверх и запустите go test. Убедитесь,
что все тесты пройдены.

Как это работает…
Этот рецепт является примером сравнения критического пути кода.
Например, иногда ваше приложение должно выполнять определенные
функции часто, может быть, каждый вызов. В этом случае мы
написали атомарный счетчик, который может добавлять или считывать
значения из нескольких горутин.
Первое решение использует объекты RWMutex и Lock или RLock для
записи и чтения соответственно. Второй использует пакет atomic,
предоставляющий тот же функционал из коробки. Мы делаем
сигнатуры наших функций одинаковыми, поэтому эталонные тесты
можно использовать повторно с небольшими изменениями и чтобы
они удовлетворяли одному и тому же целочисленному интерфейсу
atomic.
Наконец, мы пишем стандартные тесты для добавления значений и их
чтения. Затем мы пишем параллельный тест, который вызывает
функции добавления и чтения. Параллельный бенчмарк создаст
множество конфликтов за блокировку, поэтому мы ожидаем
замедления. Возможно неожиданно, пакет atomic значительно
превосходит RWMutex.

Распределение памяти и
управление кучей
Некоторые приложения могут значительно выиграть от оптимизации.
Возьмем, к примеру, маршрутизаторы, которые мы рассмотрим в
следующем рецепте. К счастью, набор инструментов для тестирования

Tlgm: @it_boooks

предоставляет флаги для сбора количества выделений памяти, а также
размера выделенной памяти. Может быть полезно настроить
определенные критические пути кода, чтобы свести к минимуму эти
два атрибута.
Этот рецепт покажет два подхода к написанию функции, которая
склеивает строки с помощью пробела, подобно strings.Join("a",
"b", "c"). В одном подходе будет использоваться конкатенация, а в
другом — пакет строк. Затем мы сравним производительность и
распределение памяти между ними.

Как это сделать…
Эти шаги охватывают написание и запуск вашего приложения:
В своем терминале или консольном приложении создайте новый
каталог с именем ~/projects/go-programmingcookbook/chapter14/tuning и перейдите в этот каталог.
Запустите эту команду:
$ go mod init github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter14/tuning
Вы должны увидеть файл с именем go.mod, который содержит
следующее:
module github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter14/tuning
Скопируйте тесты из ~/projects/go-programming-cookbookoriginal/chapter14/tuning или используйте это как упражнение
для написания собственного кода!
Обратите внимание, что скопированные тесты также
включают тесты, написанные позже в этом рецепте.
Создайте файл с именем concat.go со следующим содержимым:
package tuning
func concat(vals ...string) string {

Tlgm: @it_boooks

finalVal := ""
for i := 0; i < len(vals); i++ {
finalVal += vals[i]
if i != len(vals)-1 {
finalVal += " "
}
}
return finalVal

}
Создайте файл с именем join.go со следующим содержимым:
package tuning
import "strings"
func join(vals ...string) string {
c := strings.Join(vals, " ")
return c
}
Создайте файл с именем concat_test.go со следующим
содержимым:
package tuning
import "testing"
func Benchmark_concat(b *testing.B) {
b.Run("one", func(b *testing.B) {
one := []string{"1"}
for i := 0; i < b.N; i++ {
concat(one...)
}
})
b.Run("five", func(b *testing.B) {
five := []string{"1", "2", "3", "4", "5"}
for i := 0; i < b.N; i++ {
concat(five...)
}
})
b.Run("ten", func(b *testing.B) {
ten := []string{"1", "2", "3", "4", "5",

Tlgm: @it_boooks

"6", "7", "8", "9", "10"}
for i := 0; i < b.N; i++ {
concat(ten...)
}

})
}
Создайте файл с именем join_test.go со следующим
содержимым:
package tuning
import "testing"
func Benchmark_join(b *testing.B) {
b.Run("one", func(b *testing.B) {
one := []string{"1"}
for i := 0; i < b.N; i++ {
join(one...)
}
})
b.Run("five", func(b *testing.B) {
five := []string{"1", "2", "3", "4", "5"}
for i := 0; i < b.N; i++ {
join(five...)
}
})
b.Run("ten", func(b *testing.B) {
ten := []string{"1", "2", "3", "4", "5",
"6", "7", "8", "9", "10"}
for i := 0; i < b.N; i++ {
join(ten...)
}
})

}
Запустите GOMAXPROCS=1 go test -bench=. -benchmem benchtime=1s, и вы увидите следующий вывод:

$ GOMAXPROCS=1 go test -bench=. -benchmem -benchtime=1s
Benchmark_concat/one 100000000 13.6 ns/op 0 B/op 0
allocs/op
Benchmark_concat/five 5000000 386 ns/op 48 B/op 8

Tlgm: @it_boooks

allocs/op
Benchmark_concat/ten 2000000 992 ns/op 256 B/op 18
allocs/op
Benchmark_join/one 200000000 6.30 ns/op 0 B/op 0
allocs/op
Benchmark_join/five 10000000 124 ns/op 32 B/op 2
allocs/op
Benchmark_join/ten 10000000 183 ns/op 64 B/op 2 allocs/op
PASS
ok github.com/PacktPublishing/Go-Programming-CookbookSecondEdition/chapter14/tuning 12.003s
Если вы скопировали или написали свои собственные тесты,
запустите go test. Убедитесь, что все тесты пройдены.

Как это работает…
Бенчмаркинг помогает нам настраивать приложения и выполнять
определенные микрооптимизации таких вещей, как распределение
памяти. При сравнительном анализе выделений для приложений с
входными данными важно попробовать разные размеры входных
данных, чтобы определить, влияет ли это на выделения. Мы написали
две функции, concat и join. Оба объединяют variadic строковый
параметр с пробелами, поэтому аргументы (a, b, c) возвращают строку
a b c.
Подход concat реализует это исключительно за счет конкатенации
строк. Мы создаем строку и добавляем строки в список и пробелы в
цикле for. Мы опускаем добавление пробела в последнем цикле.
Функция join использует внутреннюю функцию Strings.Join для
более эффективного выполнения этой задачи в большинстве случаев.
Может быть полезно сравнить стандартную библиотеку с вашими
собственными функциями, чтобы лучше понять компромиссы между
производительностью, простотой и функциональностью.
Мы использовали вспомогательные тесты для проверки всех наших
параметров, которые также отлично работают с табличными тестами.
Мы можем видеть, как подход concat приводит к гораздо большему
распределению, чем join, по крайней мере, для входных данных

Tlgm: @it_boooks

одинарной длины. Хорошим упражнением было бы попробовать это со
входными строками переменной длины, а также с рядом аргументов.

Использование fasthttprouter и
fasthttp
Хотя стандартная библиотека Go предоставляет все необходимое для
запуска HTTP-сервера, иногда вам необходимо дополнительно
оптимизировать такие вещи, как маршрутизация и время запроса. В
этом рецепте будет рассмотрена библиотека, ускоряющая обработку
запросов, называемая fasthttp (https://github.com/valyala/fasthttp), и
маршрутизатор, который значительно повышает производительность
маршрутизации, называемый fasthttprouter (https://github.com/buaaz
p/fasthttprouter). Хотя fasthttp работает быстро, важно отметить, что
он
не
поддерживает
HTTP/2
(https://github.com/valyala/fasthttp/issues/45).

Как это сделать…
Эти шаги охватывают написание и запуск вашего приложения:
В терминале или консольном приложении создайте новый каталог
с именем ~/projects/go-programmingcookbook/chapter14/fastweb и перейдите в этот каталог.
Запустите эту команду:
$ go mod init github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter14/fastweb
Вы должны увидеть файл с именем go.mod, который содержит
следующее:
module github.com/PacktPublishing/Go-ProgrammingCookbook-Second-Edition/chapter14/fastweb
Скопируйте тесты из ~/projects/go-programming-cookbookoriginal/chapter14/fastweb или используйте это как
упражнение для написания собственного кода!
Создайте файл с именем items.go со следующим содержимым:

Tlgm: @it_boooks

package main
import (
"sync"
)
var items []string
var mu *sync.RWMutex
func init() {
mu = &sync.RWMutex{}
}
// AddItem adds an item to our list
// in a thread-safe way
func AddItem(item string) {
mu.Lock()
items = append(items, item)
mu.Unlock()
}
// ReadItems returns our list of items
// in a thread-safe way
func ReadItems() []string {
mu.RLock()
defer mu.RUnlock()
return items
}
Создайте файл с именем handlers.go со следующим
содержимым:
package main
import (
"encoding/json"
"github.com/valyala/fasthttp"
)
// GetItems will return our items object
func GetItems(ctx *fasthttp.RequestCtx) {
enc := json.NewEncoder(ctx)

Tlgm: @it_boooks

items := ReadItems()
enc.Encode(&items)
ctx.SetStatusCode(fasthttp.StatusOK)
}
// AddItems modifies our array
func AddItems(ctx *fasthttp.RequestCtx) {
item, ok := ctx.UserValue("item").(string)
if !ok {
ctx.SetStatusCode(fasthttp.StatusBadRequest)
return
}
AddItem(item)
ctx.SetStatusCode(fasthttp.StatusOK)
}
Создайте файл с именем main.go со следующим содержимым:
package main
import (
"fmt"
"log"
"github.com/buaazp/fasthttprouter"
"github.com/valyala/fasthttp"
)
func main() {
router := fasthttprouter.New()
router.GET("/item", GetItems)
router.POST("/item/:item", AddItems)
fmt.Println("server starting on
localhost:8080")
log.Fatal(fasthttp.ListenAndServe("localhost:8080",
router.Handler))
}
Запустите команду go build.
Запустите команду ./fastweb.

Tlgm: @it_boooks

$ ./fastweb
server starting on localhost:8080
В отдельном терминале протестируйте его с помощью некоторых
команд curl:
$ curl "http://localhost:8080/item/hi" -X POST
$ curl "http://localhost:8080/item/how" -X POST
$ curl "http://localhost:8080/item/are" -X POST
$ curl "http://localhost:8080/item/you" -X POST
$ curl "http://localhost:8080/item" -X GET
["hi","how", "are", "you"]
Файл go.mod может быть обновлен, и теперь файл go.sum должен
присутствовать в каталоге рецептов верхнего уровня.
Если вы скопировали или написали свои собственные тесты,
запустите go test. Убедитесь, что все тесты пройдены.

Как это работает…
Пакеты fasthttp и fasthttprouter могут многое сделать для
ускорения жизненного цикла веб-запроса. Оба пакета в значительной
степени оптимизируют «горячий путь» кода, но с досадной оговоркой,
заключающейся в переписывании ваших обработчиков для
использования нового объекта контекста, а не традиционного средства
записи запросов и ответов.
Существует ряд фреймворков, использующих аналогичный подход к
маршрутизации, а некоторые из них напрямую включают fasthttp.
Эти проекты хранят актуальную информацию в своих файлах README.
В нашем рецепте реализован простой объект list, к которому мы
можем добавить одну конечную точку и который будет возвращен
другой. Основная цель этого рецепта — продемонстрировать работу с
параметрами, настроить маршрутизатор, который теперь явно
определяет поддерживаемые методы вместо общих Handle и
HandleFunc, и показать, насколько они похожи на стандартные
обработчики, но имеют множество других преимуществ.