Академический Документы
Профессиональный Документы
Культура Документы
Go на практике
Go на практике
CO IN PRACTICE
11
MA NN I N G
S helter Is l a m d
Мэтт Батчер, Мэтт Фарина
GO НА ПРАКТИКЕ
Москва, 2017
УДК 004.438GO
ББК 32.973.26-018.1
Б28
УДК 004.438GO
ББК 32.973.26-018.1
Copyright © Manning Publications Со. 201 fi. First published in the English
language under the title ‘Go in Practice (9781633430075)’.
Все права защищены. Л ю бая часть этой книги нс может быть воспроиз
ведена в какой бы то ни было форме и какими бы то ни было средствами без
письменного разрешения владельцев авторских прав.
М атериал, изложенный в данной книге, многократно проверен. Но по
скольку вероятность технических ошибок все равно существует, издательство
не может гарантировать абсолютную точность и правильность приводимых
сведений. В связи с этим издательство не несет ответственности за возможные
ошибки, связанные г. использованием книги.
П редисловие ................................................................................................ 9
Введение................................................................................ 11
Благодарности......................................................................12
О книге.................................................................................. 15
Об авторах ....................................................................................................17
Об изображении на облож ке.............................................18
Г л а в а 2 . Н а д е ж н а я о с н о в а ................................................................51
2.1. CLI-приложения на G o .............................................................................52
2.1.1. Флаги командной строки..................................................................52
2.1.2. Фреймворки командной строки......................................................59
2.2. Обработка конфигурационной информации.......................................65
2.3. Работа с действующими веб-серверами................................................ 73
2.3.1. Запуск и завершение работы сервера............................................ 74
2.3.2. Маршрутизация веб-запросов......................................................... 79
2.4. Итоги............................................................................................................90
Г л а в а 3 . П а р а л л е л ь н ы е в ы ч и с л е н и я в G o ............................92
3.1. Модель параллельных вычислений в Go.............................................. 92
3.2. Работа с сопрограммами...........................................................................93
3.3. Работа с каналами....................................................................................108
3.4. Итоги..........................................................................................................122
Г л а в а 5 . О т л а д к а и т е с т и р о в а н и е ............................................. 155
5.1. Определение мест возникновения ошибок.........................................156
5.1.1. Подождите, где мой отладчик?..................................................... 156
5.2. Журналирование.......................................................................................157
5.2.1. Журналирование в G o..................................................................... 157
5.2.2. Работа с системными регистраторами.........................................168
5.3. Доступ к трассировке стека................................................................... 173
5.4. Тестирование............................................................................................. 176
5.4.1. Модульное тестирование................................................................177
5.4.2. Порождающее тестирование.......................................................... 183
5.5. Тестирование производительности и хронометраж.......................... 186
5.6. Итоги..........................................................................................................194
Содержание 7
Ч а с т ь III. И н т е р ф е й с ы п р и л о ж е н и й ........................................195
Г л а в а 6 . П р и е м ы р а б о т ы с ш а б л о н а м и H TM L
и э л е к т р о н н о й п о ч т ы ..........................................................................196
6.1. Работа с HTM L-шаблонами.................................................................. 197
6.1.1. Обзор пакетов для работы с HTM L-разметкой
в стандартной библиотеке........................................................................197
6.1.2. Добавление функциональных возможностей в шаблоны...... 199
6.1.3. Сокращение затрат на синтаксический разбор шаблонов...... 203
6.1.5. Соединение шаблонов.....................................................................207
6.2. Использование шаблонов при работе с электронной почтой........ 218
6.3. Итоги..........................................................................................................220
Г л а в а 8 . Р а б о т а с в е б - с л у ж б а м и ...............................................255
8.1. Использование R EST APT......................................................................256
8.1.1. Использование HTTP-клиента..................................................... 256
8.1.2. При возникновении сбоев.............................................................. 258
8.2. Передача и обработка ошибок по протоколу H T TP.........................263
8.2.1. Генерация пользовательских ошибок.......................................... 264
8.2.2. Чтение и использование пользовательских сообщений
об ошибках.................................................................................................. 267
8.3. Разбор и отображение данных в формате JS O N ............................... 270
8.4. Версионирование R EST API................................................................. 274
8.5. Итоги..........................................................................................................279
Когда я услышал, что Мотт Фарина и Мотт Батчер начали работу над
новой книгой о языке Go, это меня очень взволновало. Они оба много
лет были важнейшими действующими лицами в экосистеме Go, об
ладают большим опытом работы и способны добавить в аромат со
держания этой книги запахи специй прошлых учебных пособий. Эта
книга должна стать преемницей книги «Go in Action», развивающей
заложенные там основы и переводящей их в практическое русло.
Книга разбита на четыре простые в освоении части, каждая из ко
торых имеет собственную направленность. Часть 1 освежает в памяти
основные идеи языка Go. Если вы спешите и уже обладаете навыка
ми, позволяющими уверенно писать код на языке Go, можете смело
пропустить этот раздел, хотя я не рекомендую этого делать. Знако
мясь с окончательным вариантом рукописи, я обнаружил в ней само
родки такого размера, что, полагаю, главы этой части будут полезны
всем читателям.
Часть 2 погружает читателя в недра механики управления Go-
приложениями. Глава об ошибках является одним из лучших описа
ний Go-ошибок из всех, прочитанных мной прежде, а глава, посвящен
ная отладке pi тестированию, содержит массу полезной информации
об этом важном этапе разработки, помогающем поднять приложение
с уровня, требующего доказательства идеи, до уровня надежной про
изводственной системы.
В третьей части вы узнаете о способах создания пользовательских
интерфейсов. Глава но шаблонам является отличным руководством
по самой сложной, как многие полагают, части экосистемы Go. Она
знакомит с практическими приемами многократного использования
шаблонов и создания выразительных веб-интерфейсов. Приведен
ные примеры соответствуют уровню книги, поскольку трудно найти
примеры использования шаблонов, легко переносимые в реальные
приложения. Затем вы увидите, как создавать и использовать REST
API, и познакомитесь с хитростями управления версиями этого API.
Заключительная часть книги посвящена теме функциональной
совместимости, необходимой практически любому современному
приложению. Она позволяет глубоко погрузиться в облачную инф
раструктуру и увидеть, как язык Go вписывается в модель облачных
10 Предисловие
К а к организована книга
Одиннадцать глав разделены на четыре части.
Часть I «Основные понятия и принципы» закладывает фундамент
для будущих приложений. Глава 1 описывает основы языка Go и бу
дет полезна всем, кто еще нс знаком с ним или хотел бы узнать о нем
больше. В главе 2 рассказывается о создании консольных приложе
ний и серверов, а в главе 3 - об организации параллельных вычисле
ний в Go.
Часть II «Падежные приложения» включает главы 4 и 5, охваты
вающие темы ошибок, аварий, отладки и тестирования. Цель этого
раздела рассказать о создании надежных приложений, способных
автоматически справляться с возникающими проблемами.
Часть III «Интерфейсы приложений» содержит три главы и охва
тывает диапазон тем, от генерации разметки H TM L и обслуживания
ресурсов до реализации различных API и работы с ними. Многие
Go-приложения поддерживают взаимодействие через веб-интерфейс
и R EST API. Эти главы охватывают приемы, помогающие в их кон
струировании.
Часть IV «Облачные приложения» содержит остальные главы, по
священные облачным вычислениям и генерации кода. Язык Go разра
батывался с учетом нужд облачных технологий. В этой части демон
стрируются приемы, позволяющие работать с облачными службами
и приложениями и даже с микрослужбами в них. Кроме того, она
охватывает приемы генерации кода и метапрограммирование.
В книге описывается 70 приемов, и в каждом случае сначала ста
вится задача, затем дается решение и в заключение обсуждаются при
чины выбора тех или иных подходов.
16 О книге
Авторский интернет-форум
Приобретая книгу «G o на практике», вы получаете свободный доступ
к закрытому веб-форуму издательства Manning Publications, где м ож
но оставить отзыв о книге, задать технические вопросы и получить
помощь авторов или других пользователей. Чтобы получить доступ
к форуму и зарегистрироваться на нем, перейдите на страницу www.
m anning.com /books/go-in-practice. Здесь вы найдете информацию
о том, как пользоваться форумом после регистрации, какого рода по
мощь можно получить и правила поведения на форуме.
Издательство Manning, идя навстречу пожеланиям своих читате
лей, предоставляет площадку для конструктивного диалога между
читателями, а также между читателями и авторами. Это не обязывает
авторов к участию в нем —любое их участие является добровольным
(и никак не оплачивается). Задавайте авторам сложные вопросы, что
бы заинтересовать их!
Авторский интернет-форум и архивы предыдущих дискуссий будут
доступны на веб-сайте издателя, пока книга продолжает печататься.
Об авторах
ОСНОВНЫЕ ПОНЯТИЯ
И ПРИНЦИПЫ
Введение в язык Go
inport (
"fmt"
func Nair.es() (strin g, string) <r О Определена как возвращающая две строки
return "Foo", "Bar" <- О Возвращаются две строки
func main() {
nl, n2 := Names () © Значения присваиваются двум переменным
fmt. Frintln (til, п2) и выводятся
func main() {
nl, n2 := Names () < - О Значения присваиваются двум переменным
fmt.Printlnlnl, п2)
: nport. (
"buiic"
":m t"
"net"
func main() {
conn, _ := net. Dial ("tcp", "gclar.g.org:80") <- О Соединение no TCP
fmt.Fprintf(conn, "GET / HTTP/1. C\r\n\r\n") < - © Отправка строки через
statu s, _ := соединение
**bufio .NewReader (ccr.r.) .Readstring (' \n ') © Вывод первой строки ответа
fmt. Frin tln (statu s)
import (
"tint"
"io /io u til"
"net/http"
func mainO {
resp, _ :=
‘• http.Get ("http:.//example.c o n /") <- Создание HTTP-запроса GET
body, _ :=
‘•ioutil.ReadAll(resp.Body) <- Чтение тела ответа
fm t.Frintln(string(body) ) Вывод тела в виде строки
resp.Body.Close () <- Закрытие соединения
H TM L
Работая над созданием веб-сервера, невозможно обойти стороной
разметку HTML. Пакеты html и html/template отлично справляются
с формированием веб-страниц. Пакет html имеет дело с экраниро
ванной и неэкранированной HTM L-разметкой, а пакет htir.l/template
предназначен для создания многократно используемых HTML-
шаблонов. Модель безопасности обработки данных прекрасно доку
ментирована, и имеется ряд вспомогательных функций для работы
с HTML, JavaScript, и многим другим. Система шаблонов поддержи
вает возможность расширения, что делает ее идеальной основой для
реализации более сложных действий.
28 Введение в язык Go
Криптография
В настоящее время криптография стала обычным компонентом
приложений, используемым для работы с хэшами или шифрования
конфиденциальной информации. Язык Go предоставляет часто ис
пользуемые функциональные возможности, включающие поддержку
MD5, нескольких версий Secure Hash Algorithm (SH A), Transport Layer
Security (TLS), Data Encryption Standard (DES), Triple Data Encryp
tion Algorithm (TDEA), Advanced Encryption Standard (AES, прежний
Rijndael), Keyed-Hash Message Authentication Code (HMAC) и многих
других. Кроме того, в нем имеется криптографически безопасный ге
нератор случайных чисел.
Кодирование данных
При совместном использовании данных несколькими системами
встают типичные для современных сетей задачи кодирования, такие
как: обработка данных в формате baseG4, преобразование данных
в формате JS O N (JavaScript Object Notation) или X M L (Extensible
Markup Language) в локальные объекты.
Язык Go разрабатывался с учетом необходимости решать задачи
кодирования. Внутренне Go использует только кодировку UTF-8. Это
неудивительно, поскольку создатели Go прежде занимались создани
ем UTF-8. Но далеко не всегда при обмене между системами исполь
зуется кодировка UTF-8, поэтому приходится иметь дело с самыми
разными форматами данных. Для их обработки в языке Go имеются
соответствующие пакеты и интерфейсы. Пакеты поддерживают та
кие возможности, как преобразование строк JSON в экземпляры объ
ектов, а интерфейсы обеспечивают переключение между кодировка
ми и добавление новых способов работы с кодировками посредством
подключения внешних пакетов.
Сопрограммы - функции,
выполняющиеся
до-подпрограмма ^ конкуренте, - рабшакл
в потоках, управляемых
до-подпрограмма планировщиком Go
Планировщик Go способен
^ до-подпрограмма ^ перемещать сопрограммы
между потоками операци
... онной системы. Если поток
заблокирован, скажем
до-подпрограмма ^ операцией ввода-вывода,
другие сопрограммы могут
Поток перемещаться в другие
потоки
Обрабатывающее ядро
H e llo W o r ld 2
3
4
30 Введение в язык Go
func main() {
qo count () <- 0 Вызов до-подпрограммы
time.Sleep(time.Millisecond * 2)
fmt.Frintln("Hello World")
time.Sleep(time.Mi Hi second * 5)
func printCcur.t (c chan int) [ <- О Канал для передачи целого значения
пшп := О
for num >= 0 {
num = <-с <- © Ожидание целого значения
fmt.Print(num, " ")
func main() {
c := make(chan int) 4 - © Создание канала
a := lJin t{8, 6, 7, ь, 3, 0, 9, -1}
go printCount(c) < - © Вызов сопрограммы
for , v := range a <- © Запись целого значения в канал
Управление пакетами
Диспетчеры пакетов существуют для многих современных языков
программирования, но у скольких из них функция управления паке
тами является встроенной? В языке Go она встроенная, и тому есть
две веские причины. Самая очевидная - продуктивность программис
та. Вторая причина - ускорение компиляции. Управление пакетами
разрабатывалось с учетом нужд компилятора. Это один из аспектов,
определяющих быстроту компиляции.
Идею пакетов в Go проще всего изучать па примере стандартной
библиотеки (ее демонстрирует следующий листинг), которая органи
зована на основе системы управления пакетами.
func main() {
fmt. F r i n t l n ("Hello World!") <- Использование функции пакета fmt
Тестирование
Тестирование - общепринятый элемент разработки программ
ного обеспечения, и по мнению некоторых - весьма существенный.
В Go имеется полноценная система тестирования, включающая пакет
в стандартной библиотеке, средс тво выполнения тестов из командной
строки, средство оценки охвата кода тестами и средство обнаружения
состояний «гонки за ресурсами».
Процесс создания и выполнения тестов достаточно прост, как по
казано в следующем листинге.
im port "fmt"
func qetNarr.eO s t r i n g {
r e t u r n "World!"
func main() {
name := cetNsmei)
f m t . F r i n t i n ( " H e l l o ", name}
Примечательные особенности языка Go ❖ 35
Охват кода
Помимо выполнения тестов, система тестирования поддерживает
создание отчетов с полной детализацией охвата кода тестами, вплоть
до уровня инструкций, как показано на рис. 1.14.
ПРИМЕЧАНИЕ В версии Go 1.5 команды получения оценки охвата
тестам и стали частью ядра Go. Д о версии 1.5 команда cover о тно
силась к дополнительны м инструментам.
package main
im p o rt “ fm t"
fu n c getName() s t r in g {
r e tu r n “ W o rld !"
>
fu n c m a in () {
name := getNamef)
f m t . P r in t ln ( " H e l lo ” , name)
>
Форматирование
Как предпочтительнее оформлять отступы в исходном коде, сим
волами табуляции или пробелами? Вопросы форматирования и сти
ля затрагиваются и обсуждаются всякий раз, когда речь заходит о со
глашениях оформления кода. Сколько можно было бы сэкономить
времени, отказавшись от этих дебатов? Пользователям языка Go нет
смысла тратить время на обсуждение форматирования или других
языковых идиом.
На странице http://golang.org/doc/effective_go.htnnl можно озна
комиться с руководством «Effective Go» , посвященным идиомати
ческим особенностям языка Go. В нем описаны стили и соглашения,
используемые всеми членами G o-сообщества. Эти соглашения облег
чают чтение и изменение написанных на языке Go программ.
Среда Go включает встроенный инструмент форматирования, под
держивающий большинство рекомендаций по оформлению. Доста-
1
Краткий пересказ па русском можно найти по адресу: http://golang-club.
blogspot.ru/2016/03/effective-go. html. - Прим. ред.
38 Введение в язык Go
1.3.1. С и Go
Язык Go с самого начала рассматривался как альтернатива языку С
для разработки приложений. Поскольку источником вдохновения
Мес I о языка Go среди дру| их языков i ipor раммирования ❖ 39
С + Go = ego
Язык Go поддерживает использование библиотек, написанных на С,
в программах на Go. В состав Go входит библиотека инструментов
поддержки совместимости с языком С. Эта библиотека облегчает, на
пример, переход от строк в стиле С к строкам языка Go. Кроме того,
инструменты Go позволяют компоновать программы из исходного кода
на языках С и Go. Язык Go также поддерживает обертки Simplified Wrap
per and Interface Generator (SWIG). Получить общее представление об
имеющихся возможностях можно с помощью команд со help с и go doc ego.
1.3.2. Java и Go
Jav a долгое время является одним из самых популярных языков про
граммирования и используется для разработки широкого спектра
проектов, от серверных до мобильных Android-приложений и кросс -
платформсниых приложений для настольных компьютеров. Go из
начально разрабатывался как системный язык. С течением време
ни стало возможным использовать его для разработки мобильных
и всб-ириложсиий, тем не менее Go нс позволяет с легкостью писать
настольные приложения. Его превосходные качества проявляются
только при использовании по назначению.
Учитывая популярность Jav a и возможность его применения для
создания широкого спектра приложений, почему у кого-то может воз
никнуть желание пользоваться языком Go? Языки Java и Go имеют
схожий синтаксис, но в действительности они сильно отличаются.
Программы на Go компилируются в единственный двоичный файл,
предназначенный для определенной операционной системы. Он со
держит среду выполнения Go, все импортированные пакеты и само
приложение, то есть все, что необходимо для выполнения програм
мы. В Jav a используется другой подход. Приложения на Java требуют
установки среды выполнения в операционной системе. Они упако
вываются в файл, который может выполняться в любой системе с со
ответствующей средой выполнения. Приложения требуют наличия
совместимой версии среды выполнения.
Это различие подходов, проиллюстрированное на рис. 1.5, опре
деляет способ использования приложений. Для развертывания Go-
Место языка Go среди дру| их языков 11рограммирования ❖ 41
Процесс
Запрос к приложению на Python
или РНР поступает на веб-сервер. Веб Процесс
Затем веб-сервер передает его сервер
одному из процессов
Процесс
Операционная система
Работа с пакетами
Экосистема Node.js включает сообщество и инструментальные
средства для обработки и распространения пакетов. Они могут варьи
роваться от библиотек до утилит командной строки и полноценных
приложений. В комплект Node.js обычно входит диспетчер пакетов
npm. Центральный репозиторий с информацией о доступных пакетах
находится па сайте w w w .n p m js.o rg . Когда поступает команда загру
зить пакет, диспетчер извлекает метаданные о нем из центрального
репозитория и загружает пакет из исходного месторасположения.
Как упоминалось ранее, язык Go имеет собственную систему рабо
ты с пакетами. В отличие от Node.js, где метаданные и дополнитель
ная информация располагаются в центральном репозитории, язык Go
не имеет центрального хранилища, и пакеты загружаются из исход
ного местоположения.
П0Д1 ОI GBKd И ddl lyLK I ipOl pdM M bl Hd ЯЗЫКе Go 45
1.4.1. Установка Go
Установка Go - довольно простая процедура. Вся необходимая ин
формация о получении и установке Go приводится на странице
http://golang.org/doc/install. Здесь перечисляются поддерживаемые
операционные системы, аппаратное обеспечение и многое другое.
Для Microsoft Windows и Mac O S X имеются инсталляторы, кото
рые позаботятся об установке. Этот процесс настолько же прост, как
установка любой другой программы. Пользователи Homebrew на OS
X могут установить Go командой brew i n s t a l l go.
Установка Go в Linux предусматривает более широкий диапазон
вариантов. Установить Go можно с помощью встроенного диспет
чера пакетов, такого как a p t-g e t или yum. Но, как правило, они уста
навливают не самую последнюю версию, притом, что новые версии
работают быстрее и поддерживают новые функциональные возмож
ности. Следуя инструкциям по установке Go, молено загрузить са
мую последнюю версию языка, разместить ее в системе и добавить
пути к исполняемым файлам в переменную окружения PATH. В вер
сиях Linux, поддерлсивающих систему управления пакетами Debian,
таких как Ubuntu, установить последнюю версию Go можно с по
мощью godeb. Пояснения автора godeb о процедуре установки мож
но найти на странице http://blog.labix.org/2013/06/15/in-flight-deb-
pack ag es-o f-g o .
4Б Введение в язык Go
1.Б. И тоги
Язык Go предназначен для работы на современном аппаратном обе
спечении и разработки современных приложений. Он использует
последние технологические достижения и инструменты, упрощаю
щие процесс разработки. В этой главе мы рассмотрели основные до
стоинства языка программирования Go и познакомились:
О с философией языка Go, заключающейся в простоте и расши
ряемости, позволившей создать удобный язык и окружающую
его экосистему;
О с особенностями языка Со, помогающими использовать пре
имущества современного оборудования, такими как сопро
граммы, обеспечивающие параллельное выполнение;
О с сопровождающим язык Go набором инструментов, включаю
щих средства тестирования, управления пакетами, форматиро
вания и документирования;
О с характеристиками Go в сравнении с такими языками, как С,
Java, JavaScript, Python, РН Р и другими, а также узнали, для
каких задач он подходит лучше всего;
О с порядком установки и использования Go.
50 Введение в язык Go
Надежная основа
Оконные приложения
Go, как системный язык, не имеет встроенной поддержки для созда
ния оконных приложений, подобных тем, что используются в Microsoft
Windows или Mac OS X. В Go-сообществе велись эксперименты по их
разработке, но они не имели четкой направленности.
Листинг 2.2 ♦> Исходный код приложения Hello World, использующ его
пакет flag: flag_cli.go
package ir.ain
inport (
"flag" <- Импорт стандартного пакета flag
О Создание новой
I переменной из флага
func i n i t () {
flag.EoclVar(Sspanish, "spanish", fa ls e , "Use Spanish language.")
flag.BoolVar (Sspanish, " s " , "a lse , "Use Spanish language.")
GO-FLAGS
Па к е т
Некоторые пакеты обработки флагов, разработанные r сообщест
ве, нарушают соглашения, принятые в пакете flag из стандартной
библиотеки. Одним из таких пакетов является пакет github.com/
jessevdk/go-flags. Он поддерживает обработку флагов в стиле Linux
и BSD, обеспечивая еще больше возможностей, чем в пакете gnuflag, но
использует совершенно другой интерфейс и стиль. Он поддерживает
следующие возможности:
О короткие флаги, такие как -f, и группы коротких флагов, такие
как -fg;
О многосимвольные, или длинные, флаги, такие как - -flag;
О группы флагов;
О автоматическое создание справочного описания в удобной
форме;
О поддержка значений флагов в нескольких форматах, например:
-p/usr/local, -р /u sr/local и -p=/usr/'lccal;
О один и тот же параметр может извлекаться многократно и раз
биваться на фрагменты.
58 Надежная основа
"fmt"
flags "github. com/;essevdk/go-flags" <- Импорт пакета go-flags Сприсвоением
) псевдонима flags
v a r o p ts s t r u c t { О Структура
Name s t r i n g ' s h o r t : " n " lo n g :" n a m e " d e f a u l t : " W o r l d " с определениями
'• ■ d e s c r i p t i o n : " A name t o s a y he__o t c . " ' флагов
Spanish bool 's h o rt:" s " long:"spar.ish"
'•description:"Use Spanish Language"'
func main() {
fla g s .F a r s e ( s o o t s ) <- © Извлечение значений флагов в структуру
Обсуж дение
Сочетающий в себе поддержку маршрутизации, парсинг флагов,
извлечение параметров и возможность подготовки справочной доку
ментации, фреймворк c li .до обеспечивает простейший способ разра
ботки консольных приложений.
Простое консольное приложение
Перед знакомством с консольным приложением, поддерживаю
щим несколько команд, имеет смысл остановится на консольном при
ложении, осуществляющем одно действие. Вы сможете использовать
его как фундамент для расширения возможностей и поддержки не
скольких команд и подкоманд. На рис. 2.1 изображена структура при
ложения командной строки с одним действием.
GT.ORAT, OPTIONS:
—name, -n 'World' Who to say h ello to.
—help, -h show help
—version, -v p rin t "he version
import (
"fmt"
"os"
func main() {
app := cli.NewAppi) О Создание нового экземпляра приложения
app.Name = "h e llo _ c li"
app.Usage = "Print h ello world"
app.Flags = []c li.F la g { © Настройка флагов
c li .Strin gFlag{
Marne: "name, n",
Value: "World",
Usage: "Who to say hello to.
h
Ко м ан ды и подком анды
----- 1-----
Подкоманда - команда,
вложенная в другую команду
import (
" fir.t"
"os"
" q cp k q .in /u rfav e/cli.v l"
func main() {
app : = c l i .NewApp')
app.Usage = "Count up or down."
app.Commands = [] c.1 i .Command} <- О Определение одной или нескольких команд
},
}
app.Run(os.Argo)
https://12factor.net/ru/. - Прим.ред.
OOpdOotKd конфи! ypdLil/IG H H O I/l HHtJjOpMdUHH ❖ 67
О бсуж дени е
Р ассм о тр и м J S O N -ф ай л config. j son со сл едую щ и м содерж и м ы м :
"enabled": true,
"path": "/u sr/lo cal"
func mainO {
file, _ := os. Open ("co nf . j son") «- © Открытие конфигурационного файла
defer file.C lose()
decoder := j son. NewDecoder (file) © Извлечение JSO N-значений в переменные
conf := configuration{}
err := decoder.Decode(iconf)
i f e^r != nil {
fm t.Println("Error:", err)
Реш ение
import (
"fmt"
"github.com/kylelemons/go-gypsy/yaml" «- О Импорт пакета YAM L сторонних
) производителей
func main() {
config, err := yaml.ReadFile ("cor.f .yaml") <- © Чтение и анализ YAM L-файла
i f err != n il {
fm t.Println(err)
; Сорока комментария
; Section]
enabled = true
path = /usr/local if еще один кошентарий
Ф р агм ен т н следующ ем листинге извлекает и выводит данные из
этого файла.
import (
"fltlt."
"gopkg. i г/gcfg. v1" +- О Импорт пакета для работы с INI-файлами
tunc main() {
config := struct ( © Создание структуры для хранения
Section struct { конфигурационных значений
Enabled bool
Path string
func main() {
h ttp. Handl е Func (" /", homePage)
http.ListenAndServe ( " : "+os.Getenv("PORT"), ni_) <- О Извлечение значения
переменной PORT
из окружения
func hoir.eFaqe(res http.ResponseWriter, req * http.Request) {
i f req.URL.Path != " /" {
http.NotFound(res, req)
return
:nport (
"fmt."
"net/http"
"os"
)
func mainO {
http.HandleFuns ("/shutdown", shutdown) Регистрация специального пути
http.HandleFunsi"/", homePaqe) для завершения работы сервера
http.ListeriAndServe(":8080", nil)
Обсуж дение
В Braintree, подразделении компании PayPal, создан пакет manners,
позволяющий корректно завершить работу с использованием того же
интерфейса, что предоставляет функция L iste n A n d S e rv e из стандарт-
нош пакета h ttp . Внутренне пакет использует сервер из стандартного
пакета h t t p и контролирует соединения с помощью функции /Jai tGroup
из пакета sync. Метод W aitG rcup предназначен для управления сопро
граммами. Следующий листинг демонстрирует реализацию простого
сервера, использующего пакет manners.
Л и с ти н г 2.15 ❖ Штатное завершение работы с помощью пакета
manners: nanners_shu:down. go
package main
inport (
"fmt"
"net/http"
"os"
"os/sign al"
"github.com/braintree/nanners"
func mainO {
handler := newHandler() <- О Получение экземпляра обработчика
http://example.com/foo#bar?baz=quo
Р е ш е н и е : н еско лько о б ра бо тч и к о в
Данное решение расширяет прием из листинга 1.16 и использует
функции-обработчики для каждого из путей. Оно было представле
но в руководстве «Writing Web Applications» ( h ttp ://g o lan g .o rg /d o c /
articles/w iki/) и реализует шаблон, подходящий для веб-приложений
с несколькими простыми путями. Такой подход имеет определенные
ограничения (о них вы узнаете чуть ниже), которые могут вынудить
вас использовать другой прием, описываемый вслед за этим.
О бсуж д ени е
Начнем с простой программы, представленной в следующем
листинге и демонстрирующей использование нескольких обработ
чиков.
Л истинг 2.16 •> Несколько функций-обработчиков: m u ltip lejiand lers.go
package ir.ain
inport (
"fm t"
"net /http"
"strings"
func main() {
http.HandleFunc("/hello", hello) О Регистрация обработчиков
http.Handlet'unc("/goodbye/", goodbye) URL-адресов
h ttp .HandleFunc("/ ", homePage)
http.ListenAndServeC':8080", nil) ч- Запуск веб-сервера,
подключенного кпорту 8080
func hello(res http.ResponseWriter, rec * http.Request) ( ч- © Функция-
query := req.'JRL.Query () © Получение имени обработчик
паше := query.Get ("папе") ИЗстроки запроса для пути/hello
i f паше == "" {
name = "Inigo Montoya"
import (
"fnt"
"net/http"
"path" <- Импорт пакета path для обработки URL-путей
"strings"
)
func mainO {
pr := newPathResolver () -с- О Получение экземпляра маршрутизатора
pr.Add ("get /hello", hello) I © Отображение путей в функции
pr.Add("* /goodbye/*", goodbye) I
http.ListenAndServe(":8080", pr) < - © Передача маршрутизатора HTTP-серверу
inport (
"fmt"
"net/http"
"regexp" ч- Импорт пакета для работы с регулярными выражениями
"strings"
)
func main() {
r r := newPathResolver()
rr.Add("GET /hello", hello) I О Регистрация
rr.Add("(GET|HEAD) /goodbye(/?[A-Za-z3-9]*)?", goodbye) | путей ифункций
http.ListenAndServe(":8080", rr)
2.4. И тоги
После реализации фундаментальных элементов можно переходить
к более специфичной части приложения, которая, по сути, и делает
его полезным. В этой главе мы познакомились с несколькими базо
выми элементами:
О удобная обработка параметров командной строки. Здесь был
охвачен диапазон от легковесных решений до простых фрейм
ворков для создания консольных приложений и утилит;
О различные способы извлечения конфигурационной информа
ции в разных форматах из файлов и переменных окружения;
Итоги 91
Параллельные
вычисления в Go
fu n c m a in () {
qo e c h c ( o s . S t d in , o s .S td o u t) <- Вызов функции echo как до-подпрограммы
(30 * tim e .S e c o n d )
t im e .S le e p <- 30-секундная пауза
fmt. Print In ("T im ed o u t . " ) <- Вывод сообщения о завершении ожидания
o s . E x i t (0) <- Выход из программы. При этом сопрограмма будет остановлена.
f m t . P r i n t l n (" O u t s id e a g a i n .")
О бсуж дение
func main(> {
for file := range os.A rgs[l:] { Создать список файлов,
compress (file) переданных в командной строке
defer in.Close!)
out, err := os. Create (filename + ".qz") <- Открыть файл архива с расширением
i f err != n il { .gz и именем исходного файла
Работа l coi ipoi раммами ❖ 99
return e r r
defer c u t.C lo se d
Сжать данные и записать
gzout := gzip.NewWriter(out) « - в соответствующий файл с помощью gzip.Writer
e r r = io.Copy(gzout, in) «- Функция io.Copy выполняет
gzout.C lose() необходимое копирование
return e rr
func main() {
var wg sync.WaitGroup Нет необходимости инициализировать WaitGroup
var i i n t = -1 Так как переменная i необходима за пределами цикла,
var file string она объявляется именно здесь
for i, file = range os.A rgs[l:] {
wg. Add (1) <- Для каждого файла сообщить группе,
что ожидается выполнение еще одной операции сжатия
go func (filename string) Эта функция вызывает функцию сжатия
compress (filename) и уведомляет группу ожидания о ее завершении
wg. Done()
} (file) « - Поскольку вызов сопрограммы происходит в цикле for,
} требуется небольшая хитрость, чтобы передать параметр
wg.Wait () «- Внешняя сопрограмма (main) ожидает,
fm t.F rin tf ("Compressed %d file s\n ", i+1) пока все сопрограммы, выполняющие
сжатие, вызовут wg .Done
1.
func mainO {
var wg sync.waitGroup <- И снова используется группа ожидания
w := newWordsi) для мониторинга группы сопрограмм
for f := range os.Args[l:] { Главный цикл использует
wg.Add(1) прием из рецепта 12
go tunc (file string) {
if err := taliyWords(file, w); err != nil {
fmt.Println(err.Error())
104 Параллельные вычисления в Go
wc. Done f)
}(£>
wg.Wai t ()
fmt.Printin("Words that, appear more than once:") Перед завершением
for word, count. := range w.”ound { программы вывести
i f count > 1 { результаты поиска
fm t.Printf '"%s : %d\n", word, count)
Фиксирует количество
func (w *wcrds) add i word strin g , n int) { «- вхождений этого слова
count, ck := w.foundfword] Если слово еще не зафиксировано, добавим его.
i f ! ck { В противном случае увеличим счетчик
w.found[word] = n
return
w.found[word] = count + n
func tallyKcrds (filename strin g , die r *words) error { ч - Открытие файла, анализ
file, err := os.Open (filename) содержимого и подсчет
i f err != n il { найденных в нем слов.
return err Функция копирования
делает все необходимое
defer file.C lose()
goroutine 8 [running]:
runtime.threw (0x115890, Oxd)
/usr/1ccal/go/src/runt.ime/panic.go:527 +0x90 fp=0x82029cbf0
sp=0x82029cbd8
runtime.evacuate(ОхсабОО, 0x8202142aC, 0x16)
/usr/lccal/go/src/runtime/hashmap.co:325 +0x3b0 fp=0x82029ccb0
sp=0x82029cbf0
runtime.grcwWork(ОхсабОО, 0x8202142dC, 3x31)
/usr/lccal/go/src/runtime/hashmap.go:795 +0x8a fp=0x82029ccd0
sp=0x82029ccb0
runtime.mapassignl(ОхсабОО, Ox8202142dC, 0x82029ce70, 0x82029cdb0)
/usr/lccal/go/src/runtime/hashmap.go:433 +0x175 fp=0x82029cd78
sp=0x82029ccd0
Read by goroutine 8:
runtime.mapacces32_faststr()
/ tmp/ wor kdi r / go / s r c / run t ime / has nmap_f as t.go:281 +0x(J
main.tallyWords()
/Users/rabutcher/Code/go-in-practice/chapter3/race/race.go:62 +0x3ed
main.main, fundf)
/Users/mbutcher/Code/qo-in-practice/'chapter3/race/race.go:18 +0x66
Previous write by goroutine 6:
runtime.mapassignl()
/tmp/workdir/go/src/runtime/hashmap.go:411 +0x0
main.tallyWords()
/Uaers/mbutcher/Code/go-in-practice/chapter3/race/race.go:62 +0x48a
main.main.fund ()
/Users/mbutcher/Code/go-in-practic9/chapter3/race/race.go:18 +0x66
Goroutine 8 (running) created at:
main.main()
/Users/mbutcher/Code/go-in-practice/chapter3/race/race.go:22 +0x238
Goroutine 6 (running) created at:
main.main()
/Users/mbutcher/Code/go-in-practice/chapter3/race/race.go:22 +0x238
inport (
/ / Те же пакеты, что и прежде...
"sync"
func main() {
var wg sync.WaitGroup
w := newHordsO
for f := range o s.A rg s[l:] {
wg.Add(l)
go func (file strin g) {
i f err := tallyWords (file, w); e rr != n il {
fm t.P rin tln (e rr.E rro r())
}
wg. Done()
1(f)
wg.Wait()
fmt.Println("Words that appear more than once:"
w.Lcck() Установка и снятие блокировки
for word, count := range w.found { доступа к объекту до и после
if count > 1 ( итераций. Строго говоря, это
fm t.Printf("% s: ^d'vn", word, count) делать не обязательно, поскольку
этот фрагмент выполнится только
} после обработки всех файлов
w.Unlock))
count, ск := w.found[word]
i f !ск {
w.found [wordj = n return
w.found[word] = count + n
Не злоупотребляйте каналами
Каналы - фантастический инструмент связи между сопрограммами.
Они просты в использовании и значительно упрощают параллельное
программирование, по сравнению с моделью потоков выполнения
в других популярных языках.
Но их использованием не стоит увлекаться. Каналы вносят дополни
тельные затраты и могут негативно влиять на производительность.
Они усложняют программу. И самое главное, каналы - единственный
источник проблем, связанных с управлением памятью в программах
на языке Go. Как любой другой инструмент, каналы следует использо
вать, только когда в них возникает необходимость, но не при каждом
кажущимся удобным случае.
Пр о б л е м а
Использовать каналы для передачи данных между сопрограммами
и получить возможность прерывать этот процесс при выходе.
Ре ш е н и е
Используем оператор select и несколько каналов. В языке Go ка
налы часто используются для передачи сигнала, когда завершилось
выполнение какого-то задания или все готово к закрытию.
Обсуж дение
Для продолжения знакомства с каналами вернемся к первому при
меру в этой главе. Эта программа в течение 30 секунд осуществляет
эхо-вывод всего, что вводит пользователь. Эхо-вывод в ней реализо
ван в виде сопрограммы, а измерение 30-секундного интервала осу
ществляется вызовом метода time.Sleep. Перепишем эту программу,
использовав каналы в дополнение к сопрограммам.
Мы нс собираемся расширять возможности программы или как-
то улучшать ее. Мы просто хотим показать другой подход к решению
той же задачи. При этом вы увидите пример идиоматического ис
пользования каналов.
Прежде чем перейти к реализации в листинге 3.7, ознакомимся
с некоторыми использованными в ней идеями:
О каналы создаются с помощью метода make, подобно объектам
тар и slice;
О оператор стрелки (<-) используется для обозначения направ
ления канала (out chan<- [] byte) и для отправки или получения
данных (buf := <-echo);
О инструкция select может следить сразу за несколькими кана
лами. Пока ничего не происходит, она ждет (или выполняет ин
струкцию default, если определена). Когда r канале возникает
событие, select обрабатывает его. Более подробная информа
ция о каналах будет представлена ниже в этой главе.
packaqe m ain
im p o rt (
"tim e "
func main() {
t im e .S le e p ( b * tim e .S e co n d ) <- Приостановка на пять секунд
s le e p := t im e . A f t e r [5 * tim e .S e c o n d ) Создание канала для получения
c - s le e p уведомления через пять секунд
;• с последующей приостановкой
до поступления уведомления
inport (
"fmt"
"time"
)
func main() {
msg := make(chan string)
un til := tim e.A fter(5 * time.Second)
go send (msg) <- Вызов сопрограммы send с передачей канала для отправки
Работа с каналами ❖ 115
func mainO {
msg := make(chan string)
118 Параллельные вычисления в Go
done := make (chan bool) <- Дополнительный канал с типом данных bool
un til := time.After (5 * time.Second) для сообщения 0 завершении
go send(msq, done) <- Передача двух каналов в send
for (
select {
case in := <-msg:
fmt.Println(m)
case c -u n til : По истечении заданного интервала времени сообщить
done <- true <- сопрограмме send, что работа завершена
tim e.Sleep(500 * time.Millisecond)
return
func worker(id in t, lock chan bool) { единичный объем, что делает его
fm t.F rin tf("Id wants the lock\n", id) собственником блокировки. Остальные
fm t.Frintf ("Id has the lock\n", id) Фрагмент между lock <- true и <- lock
time.Sleep (500 * time.Mi l l i second) ч- выполняется ПОДзащитой блокировки
fm t.Frintf ("М is releasing the lock\n", id)
<-lcck <— Снять блокировку, прочитав значение из канала. В результате в буфере
освободится место, и следующая функция сможет установить блокировку
S go run lock.go
2 wants the lock
1 wants the lock
2 has the lcck
5 wants the lock.
6 wants the lock.
4 wants the lock.
3 wants the lock
2 i s releasin g the lock
1 has the lock
1 i s releasin g the lock
b has the lcck
b i s releasin g the lock
6 has the lcck
6 i s releasin g the lock
3 has the lcck
3 i s releasin g the lock
4 has the lock
4 i s releasin g the lock
3.4. И тоги
Эта глава была посвящена системе параллельных вычислений в язы
ке Go. Мы познакомились с сопрограммами и пакетами, обеспечи
вающими синхронизацию сопрограмм. Затем была описана мощная
система каналов Go, позволяющая нескольким сопрограммам взаи
модействовать посредством типизированных каналов. Попутно было
представлено несколько важных идиом и шаблонов, включая:
О модель параллельных вычислений в языке Go, основанную на
взаимодействующих последовательных процессах (Communi
cating Sequential Processes, C SP);
О параллельную обработку с помощью сопрограмм;
О использование пакета sync для организации ожидания и бло
кировок;
О обмен сообщениями между сопрограммами посредством кана
лов;
О корректное закрытие каналов.
В следующих главах мы рассмотрим применение сопрограмм и ка
налов на практике. Вы увидите, как веб-сервер па языке Go запускает
новую сопрограмму для обработки каждого запроса, и познакомитесь
с такими шаблонами, как распределение рабочей нагрузки между не
сколькими каналами. Если что-то и выделяет язык Go в ряду других
языков, так это прежде всего его модель параллельных вычислений.
Л теперь обратимся к обработке ошибок. Хотя это нс самая гламур
ная тема, тем не менее обработка ошибок в языке Go является одной
из его важных особенностей.
Часть
НАДЕЖ НЫ Е
ПРИЛОЖЕНИЯ
Обработка ошибок
и аварий
Вес, что содержит функцию Error, должно возвращ ать строку, как
того требует этот интерфейс. Обычно для работы с ошибками Go-
разработчикам бывает достаточно типа error. Но иногда может по
требоваться передавать в ошибках дополнительные сведения, кроме
OGpdQoiKd ошибок ❖ 131
inport (
"e rr o r s "
"fmt" Создание экземпляра
"math/rand" ошибки превышения
времени ожидания Создание экземпляра
var ErrTimecut = errors.New("The request timed o u t" )- * - 1 ошибки отказа
var E rrRejected = errors.New("The request was reje cte d ") •<-------------------------------
Не с о в с е м сл учай н о е
Одной из интересных деталей примера в листинге 4.7 является гене
ратор случайных чисел. Поскольку генератор случайных чисел ини
циализируется фиксированным значением, он всегда будет возвра
щать одну и ту же последовательность «случайных» чисел. В данном
случае это удобно, поскольку позволяет проиллюстрировать работу
примера. Но в промышленных приложениях не следует использовать
фиксированные целые числа для инициализации источника. Простой
альтернативой является использование в этом качестве time.Now.
: nport. (
"errors"
"fm t"
)
var ErrDivideByZero = errors.New("Car.'t divide by zero")
func main() {
fm t.F r in t in ("D ivide 1 by 0")
e rr := precheckD ivide(1, 0) Первое деление выполняет функиия
i f e rr != n il { precheckDivide, возвращающая ошибку
fmt.Printf("Error: %s\n", err
}
fmt. Frint in ("Divide 2 by 0") | Затем выполняется такое же деление
divide (2, 0) но с помощью функции divide
Обсуж дение
Сигнатура функции с аргументом типа interface!] не определяет
явно, что передавать ей. Можно, например, передать объект, вызвав
ший аварию. Как вы уже видели, можно передать строку, значение nil
или ошибку.
Лучше всего передать (но крайней мере, при нормальных обстоя
тельствах) то, что реализует интерфейс error. На то есть две веские
причины. Первая - очевидность. Что вызывает аварии? Ошибки.
Разумно предположить, что разработчики ожидают именно этого.
Вторая причина заключается в том, что это облегчает обработку ава
рий. Вы убедитесь в этом, когда мы начнем рассматривать приемы
восстановления после аварии, - вы узнаете, как перехватить аварию,
устранить ее последствия, а затем воспользоваться содержимым ава
рии, как эт о делается при работе со старой доброй ошибкой.
Итак, следующий листинг демонстрирует идиоматический способ
возбуждения аварии.
Л и с ти н г 4.11 ❖ Правильное возбуждение аварии
package irain
•’nport "errors"
func main0 {
panic(errors.Mew("Something bac happened.")) <- Вызов функции panic
с передачей ошибки
•’nport. "fmt"
func main() {
defer goodbye () <- Отложенный вызов функции goodbye
fmt. Pr in tln ("Hello world.") <- Вывод строки. Выполняется
до вызова функции goodbye
func goodbye О {
fmt. F r in tln ("Goodbye")
Обсуж дение
В языке Go имеется возможность получить информацию об аварии
и остановить дальнейшее раскручивание стека вызовов. Данные из
влекаются с помощью функции recover.
Система аварий ❖ 141
in p o rt (
"errors''
"fm t"
import "fmt"
func main() {
var msg str in g <- Определение переменной вне замыкания
defer fur.c() {
fmt.. P rin t! n (msg) ч - Вывод значения переменной в отложенном замыкании
И )
im port "fmt"
func main() {
defer func() {
fm t.P rin t! n (msg) ч - Вывод значения переменной
}()
Система аварий ❖ 143
Л и с т и н г 4.16 ❖ Очистка
package ir.ain
inport (
"errors"
"rmt"
"io"
"os" Вызов функции OpenCSV и обработка
) всех ошибок. Эта реализация всегда
возвращает ошибку
func main() {
var file io.ReadCloser
file, err := OpenCSV("data.csv") Использовать отложенную
i f err != n il { функцию, чтобы гарантировать
fm t.P rin tf("E rror: %s", err) return закрытие файла при любых
} обстоятельствах
defer file.Closef) --------------------------
/ / Что-то делается с файлом. «- Обычно здесь помещается код
для работы с файлом
Проблема
Если авария в сопрограмме остается необработанной, это приводит
к краху всей программы. Иногда такое допустимо, но чаще желатель
но восстановить нормальную работу.
Реш ение
Самое простое решение - реализовать обработку аварий во всех со
программах, где они могут произойти. Наше решение поможет упрос
тить разработку серверов, которые обрабатывают аварии, не полага
ясь на их обработку в каждой отдельной функции handle.
О бсуж дение
func listen () {
listen er, err := net.Listen ("tcp", ":1026") «- Запуск нового сервера,
i f err != n il { обслуживающего порт 1026
fm t.Println("Failed to open port on 1026")
return
for (
conn, err := listener.Accept.()
i f err != n il { Прием новых клиентских
go handle i conn) <- При появлении запроса передать его в функцию handle
func main() {
http. HandleFunc ("/ ", handler) Передача функции handler НТТР-серверу
http.ListenAndServe[":8080", nil) «- Запуск сервера
import (
"github.com/Masterminds/cockoo/safely" ч- Импорт пакета safely
"errors"
"time"
func message () { <- Определение функции обратного вызова, соответствующей типу GoDoer
println("In side goroutine")
panic(errors.New("Oops!"))
CncieMd аварий ❖ 153
func mainO {
safely. Go (message) <- Заменяет go message
println("Outside goroutine")
time.Sleep (1000) ч- Гарантирует выполнение сопрограммы
перед завершением работы программы
В этом примере определена простая функция, соответствую щ ая
типу GoDoer (не имеет параметров и возвращ аемого значения). Затем
вызывается метод safely.Go (message), запускающий функцию message
в новой сопрограмме и перехватывающий все аварии. Поскольку
функция message возбуждает аварию, программа после запуска вы ве
дет следующее:
s go run safely_example.go
Outside goroutine
Inside goroutine
2015/04/08 08:26:00 Panic in safely.Go: Oops!
Вместо аварийного прерывания программы sa fe ly . Go перехватыва
ет аварию и выводит сообщение о ней.
4.3. Итоги
Следует честно признать, что в обработке ошибок нет ничего гламур
ного. Но мы уверены, что по коду обработки ошибок можно отличить
просто хорошего программиста от мастера. Мастера не забывают
о системе защиты от сбоев и ошибок.
Именно поэтому целая глава была посвящена подробному рассмот
рению системы обработки ошибок и аварий в языке Go и приемам
решения возникающих при этом проблем. Мы показали наиболее
эффективные способы извлечения значимых данных об ошибках
и восстановления после аварий. И завершили главу рассмотрением
сочетания аварий с сопрограммами.
В этой главе были представлены:
О общие сведения о сценариях обработки ошибок на языке Go;
О прием использования переменных ошибок;
О способ создания пользовательских типов ошибок;
О правильное использование и обработка аварий;
О приемы обработки ошибок в сопрограммах.
Следующая глава будет посвящена другим аспектам качества
кода, таким как журналирование и тестирование. Освоив эти ин
струменты, вы станете успешным (и, может быть, даже великим) Go-
разработчиком.
Глава
Отладка
и тестирование
5.2. Ж урналирование
Давно стало общепринятой практикой записывать сведения о состоя
нии процессов r файлы журналов или подсистемы. Для всех попу
лярных языков программирования существуют библиотеки функций
журналирования, и язык Go - не исключение. Более того, разработ
чики языка Go решили включить журналирование в число основных
библиотек.
Как правило, журналирование предназначено для захвата опреде
ленных сведений, которые разработчики, системные администрато
ры, другие программы смогут использовать для анализа выполнения
приложения. Например, журнал веб-сервера должен содержать такие
сведения, как время его запуска, когда и какие необычные ситуации
возникли и как он обрабатывал запросы.
5.2.1. Журналирование в Go
Язык Go включает два встроенных пакета с функциями журналиро
вания: log и Icg/sysloc. Сначала рассмотрим первый из них, а затем,
в разделе 5.2.2, перейдем к пакету svslog.
Пакет log реализует простейшую поддержку (главным образом
в виде форматирования) записи сообщений в журнал. В простейшем
случае он форматирует сообщения и отправляет их в поток стандарт
ного вывода ошибок, как показано в следующем листинге.
in p o rt (
"log"
15В ❖ Отладка и тестирование
func main О {
log.P rin tln ("This is a regular message.") <- Запись сообщения в os.Stderr
log. Fatalln ("This is a fa ta l e rro r.") < ---------------------- Запись сообщения
log.Println("T h is is the end of she function.") в os.Stderr и выход
Эта инструкция никогда не будет выполнена с кодом ошибки
^ , Создание регистратора
func main() {
logfile, _ := os.Create!"./log.txr") «- Создание файла журнала
defer logfile.Close () «- Гарантировать закрытие
loqqer := log.New(logfile, "example ", log.LstdFlaqsllcq.Lshortfile) -----
logger.Printlni"This is a regular message.") Отсылка сообщений
logger.Fatalln("This is a fatal error.")
logger.Println("This is the end of the function.") <- Как и раньше, никогда
не будет выполнена
/ \
Ldate Llongfile
Ltim e Lshortfile
Lm icroseconds
LstdFlags
func main() {
conn, err := n et.D ial("tcp ", "Iocaino3t:1902") <- Подключение к серверу
i f err != n il { журналирования
panic("Faiied to connect to iocalhost:1902n)
О бсуж дение
func main() {
timeout := 30 * time. Second <- Явное добавление задержки
conn, err := net.DialTimeoutC'udp", "locaihost:1902", timeout) ----------
i f err != n il ( Замена ТСР-соединения
panic("Failed to connect со localhost:1902") на UDP-соединение
defer conn.Closet)
f := lcq.Ldate | loq.Lshortfile
logger := log.New(conn, "example ", f)
func main() {
logger, err := syslog.New(syslog.L0G_L0CAL3, "narwhal") *— Создание нового
if err != nil { клиента
panic("Cannot attach to syslog") системного
} регистратора
defer logger.Close()
logger. Dehug("Debug message."}
logger.Notice("Notice message.") Вывод различных сообщений
logger.Warning("Warning message.")
logger.Alert("Alert message.")
Первые два поля содержат время и имя хоста. Они генерируются си
стемным журналом. Затем идет метка narwhal. За ней следует идентифи
катор процесса (Process TD, РТП). И наконец, само сообщение. Порядок
и формат полей определяются конфигурацией системного журнала.
В примере выше мы послали четыре сообщения, но в файл попа
ли только три из них. Сообщение, отправленное вызовом функции
syslcg.Debug, пропало. Причина в том, что системный журнал в дан
ном примере настроен так, чтобы не сохранять отладочные сообщения
в файле. Если вам понадобится записывать отладочных сообщений,
придется изменить конфигурацию системного журнала. Преиму
щество такого решения в том, что разработчик не должен решать, что
отображать и при каких обстоятельствах. Это будут выбирать те, кто
использует приложение.
В этом разделе мы рассмотрели большую часть видов сообщений,
но остается еще один важный для программистов инструмент отлад
ки, позволяющий сохранить в журнале трассировку стека.
import (
"runtime/debug"
func main() { —
food
func b a r() { ◄ —
debug. PrintStack() ч - Вывод трассировки
func foc() {
bar()
func bar() {
buf := make ([]byte, 1024) <- Создание буфера
runtime. S ta c k (buf, false) e Запись стека в буфер
fmt.Pri n tf("T race :\n % s\n", buf ) <- Вывод результата
5.4. Тестирование
Тестирование и отладка требуют детального анализа сведений о про
грамме. Если отладка носит реактивный характер, то тестирование -
активный. В этом разделе рассматривается несколько подходов к ра
боте с большинством инструментов тестирования в языке Go.
5 .4 .1 . М о д ульное те сти р о в а н и е
Написание тестов вместе с кодом стало стандартным правилом разра
ботки программного обеспечения. Некоторые методики разработки
(например, разработка через написание тестов) даже ставят разработ
ку тестов во главу угла.
Большинство введений в язык Go объясняет, как писать тесты
с применением встроенных средств. Язык Go разрабатывался с уче
том тестирования и включает инструменты для запуска тестов в про
ектах. Любой файл исходного кода на языке Go, имя которого закан
чивается на _testgo, считается тестовым файлом. Для запуска этих
тестов используется инструмент go test.
Файлы, имена которых заканчиваются im _testgo, могут содержать
функции с именами, начинающимися со слова Test и требующими пе
редачи единственного параметра типа ^testing. Т. Каждая такая функ
ция будет выполняться как модульный тест. Предположим, имеется
файл с исходным кодом hellogo. Этот файл содержит единственную
функцию Hello, которая возвращает строку hello, как показано в сле
дующем листинге.
inport "testin g" ч - Пакет testing содержит встроенные инструменты тестирования языка Go
178 Отладка и тестирование
import (
"testing"
Тип MockMessage реализует
интерфейс Messager
type MockMessage struct ( -<■
email, subject strin g
body []byte
Проблем а
Ре ш ен и е
Напишем «канареечные» тесты проверки типов, неудача которых
позволит быстро выявить ошибки, допущенные при определении ин
терфейса.
Обсуж дение
При написании или реализации интерфейсов, особенно когда све
дения о типах выясняются во время выполнения, имеет смысл созда
вать канареечные тесты с простыми проверками типов, которые по
зволят выявлять ошибки уже на этапе компиляции.
Предположим, что нужно создать инструмент записи, реализую
щий интерфейс io.Writer. Этот инструмент будет экспортироваться
библиотекой, чтобы им можно было воспользоваться из другого кода.
Реализация этого инструмента приводится в следующем листинге.
Л и с ти н г 5.15 ❖ Тип Му Writer
type МуWriteг stru c t{
// ...
Это простой тест. Вам даже не придется запускать тест, чтобы вы
явить ошибку, - компилятор просто не сможет выполнить сборку:
S go te st
f _/Users/mbutcher/Code/go-in-practice/cnapter5/tests/canary
./canary_test.go:15: cannot use MyWriter lite r a l (type *MyKriter)
as type io.Writer in assignment:
*MyWriter does not implement, io.Writer (wrong type for Write method)
have Write( [Jbyte) error
want Write([]byte) (int, error)
FAIL _/Users/mbutcher/Code/go-in-practice/chapter5/tests/canary
;build failed]
Проблема
Необходимо получить надежный код, справляющийся с самыми
неожиданными ситуациями.
Ре ш ен и е
Используем пакет testir.g/quick из стандартной библиотеки Go для
генерации тестовых данных.
Обсуж дение
В языке Go имеется пакет testing, о котором часто забывают. Па
кет testing/quick включает несколько вспомогательных функций для
быстрого создания исчерпывающих тестов. Их можно применить не
во всех случаях, но иногда они помогают добиться большей надеж
ности тестирования.
Предположим, имеется простая функция, дополняющая передан
ную строку до заданной длины (или усекающая, если длина строки
превышает заданное значение). Код функции приводится в следую
щем листинге.
Л и с ти н г 5.18 ❖ Функция дополнения
func Pad(s string, .пах uint) string {
log.Frintf("T esting Ten: %d, Sr.r: %s\n", max, s) м ---------------------
Журналирование данных
i f in > max {
Если строка длиннее, для удобства
return s [::nax-1]
чем нужно,-обрезать
s += strin gs. Repeat (" ", int (max-ln)) ч - Дополнить строку до нужной длины
return s
inport (
"bytes"
"testing"
"text/template"
5.6. И тоги
Эта глава была посвящена приемам отладки и тестирования С о
программ. Мы рассмотрели инструменты журналирования, трасси
ровки стека, модульного тестирования и оценки производительности.
Все они являются фундаментальными инструментами для разработ
ки надежных программ на языке Со.
В этой главе были охвачены следующие вопросы:
О журналирование по сети;
О работа с пакетом log из стандартной библиотеки Go;
О захват трассировки стека;
О использование идиом языка Go для написания модульных тес
тов;
О оценка производительности с помощью инструментов тестиро
вания языка Go;
О простое порождающее тестирование;
О выявление состояний гонки.
В следующих главах вы познакомитесь с рецептами, посвященны
ми подходам к написанию кода.
Часть
••
И Н ТЕРФ ЕЙ СЫ
ПРИЛОЖ ЕНИЙ
Приемы работы
с шаблонами H T M L
и электронной почты
in p o r t (
" h t ir l/ t e r a p la t e " <- Использование пакета html вместо texb'template
"net/http"
func main() {
h ttp . Har.dleFunc displayPace) Обслуживание вывода с помощью
h ttp . T.i stenAndServe !": 8080", nil) простого веб-сервера
import (
"htir.l/template"
"n et/h ttp " "tim e"
)
var t p i = '<!D0CTYPE HTML> 4- HTML-шаблон в виде строки
<htnl>
<head>
<meta c h arse t= "u tf-8 ">
< title> D ate Exam ple</title>
</head>
<body>
<p>({.D ate | dateFormat "Jan 2, 2006" } }</p> <- Применение команды
</body> dateFormat к Date
</htm l>‘
func main() {
http.H andleFuncC/", serveTemplate) I Обслуживание шаблона и набора данных
h ttp.Listen A ndServe(":8080", n il) | с помощью веб-сервера
import (
"html/template"
"net/http" Синтаксический разбор
^ при инициализации пакета
var t. = tempi ate.Must(template.FarseFi1ез("templates/simple.html")) < —
type Page struct {
T itle, Content string
func main() {
ht t p.H a r d l e F u n c d i aplayFace)
http.ListenAndServe!":8080", nil)
func main() {
http.HandleFunc( , diaplayPage)
http.ListenAndServe(":8080", n il)
import (
"htir.l,/ template"
"net/http"
Загрузка двух шаблонов
var t. *templ ate. Tempi ate func in it() { Вобъект шаблона
t = template.Must(tem plate.ParseFiie3Cindex.htm l", "head.html")) < -----
func m ain() {
http.H ar.dleF uncf"/", diaplayPage) Обслуживание страницы
h ttp .L isten A n d S erv e!":8080", n il) встроенным веб-сервером
Заголовок Заголовок
Стили Стили
Сценарии Сценарии
Содержимое Содержимое
О бсуж дени е
Система шаблонов поддерживает механизм наследования. Она не
предоставляет полного диапазона возможностей, доступных в других
Работа с HTML-шаблонами ❖ 211
</style>
{ {end}}
Здесь определяется шаблон styles. Он переопределяет значение но
умолчанию в листинге 6.9.
В следующем листинге шаблоны объединяются в единое целое.
Листинг 6.12 ❖ Использование наследования шаблонов: inherit.go
package main
import (
"htir.l /tempi ate"
"net/http"
)
PdOoTd c HTML-md0AOHdMH ❖ 213
var t map [ str in g ]+template. Template <- Массив для хранения шаблонов с их именами
func i n i t () {
t = make (map [string] ^template. Template) <— Настройка карты шаблонов
temp := tem plate.M ust(tem plate.RarseFiles("base.html ", "user.htm l"))
tl"user.h tm l"] = temp
temp = template.Must (tem plate.ParseFiles ("base.htm l", "page.html"))
t [ "page.html"] = temp
Загрузка шаблонов вместе
с основным шаблоном
type Page struct {
T itle , Content strin g
t["user.html"].ExecuteTemplate(w, "base", u)
func main]) {
h ttp . Har.dleFunc("/user", displayUser)
Обслуживание страниц с помощью
http.Har.dleFunc("/", displayPage)
встроенного сервера
http.ListenAndServe(":8080", nil)
Веб-страницы отображаются
с помощью общего шаблона страницы.
Разделы с содержимым, отображае
мые с помощью собственных
шаблонов и наборов данных,
передаются в следующий шаблон
Про бл ем а
Требуется преобразовать объект в H TM L-разметку и передать ее
в шаблон более высокого уровня, где он будет использован как часть
вывода.
Р еш ен и е
Используем шаблоны для вывода объектов в виде H TM L-разметки.
Сохраним H TM L-разметку в переменной и будем передавать ее шаб
PdOoTd c HTML-md0AOHdMH ❖ 215
лонам более высокого уровня в виде значения типа template. HTML, от
меченного как надежного и не требующего экранирования.
Обсуж дение
Есть пара причин организовать вывод в несколько шагов. Во-
первых, если определенная часть страницы требует существенных
затрат на создание набора данных или IIT M L -разметки, имеет смысл
не повторять эту работу при отображении всех страниц.
Предположим, что имеется список сведений о пользователе. Он
содержит информацию о пользователе и его деятельности и может
передаваться другим пользователям для просмотра. Чтобы получить
такой набор данных, требуется выполнить поиск в нескольких источ
никах. Если сохранить эту информацию в кэше, можно исключить ее
загрз^зку при каждом просмотре страницы.
Кэширование не отменяет вывода набора данных при каждом об
ращении к странице и требует места для его хранения. Вывод данных
в таком случае заключается в их преобразовании пакетом поддержки
шаблонов в надлежащий формат с гарантированным экранировани
ем. Если кэшировать готовые фрагменты IIT M L-разметки для по
вторного использования, при каждом обращении к странице придет
ся проделать гораздо меньший объем работы.
Во-вторых, предположим, что имеется приложение со сложной
логикой и массой страниц. Оно может содержать много шаблонов
с повторяющимися фрагментами разметки. Но если распределить
обязанности между всеми шаблонами, поручив каждому что-то одно,
будь то основное содержимое, боковая панель или оболочка страни
цы. это упростит управление шаблонами.
В следующем листинге показано, как отобразить объект из шабло
на, сохранить IIT M L -разметку, а затем внедрить ее в другой шаблон.
import (
"bytes"
"htnl/template"
"net/http" Загрузка двух файлов шаблонов
) для дальнейшего использования
var t *template. Template Переменные для хранения данных,
var qc template.HTML совместно используемых запросами
func i n i t () {
t = template.Must(template.ParseFiles("index.html", "qucte.html")) <—
func main() {
q := SQuote{ Заполнение
Quote: 'You keep using that word. T do not think набора данных
i t means what you trunk i t means.', для передачи
Person: "Inigo Montoya", в шаблон
PdOoia с HTM L-шаблонами ❖ 217
I
var b bytes. Buffer ч- Запись шаблона иданных
t.ExecuteTemplateUb, "quote.html", q) Сохранение цитаты в виде
qc = template.н т (b. String ()) ч- HTML-разметкив глобальнойпеременной
h ttp . Har.dleFuno diaplayFaqe) Обслуживание вывода с помощью
http.ListenAndServe!":8080", n il) простого веб-сервера
import (
"bytes"
"net/smtp"
Ии Ю ЛЬВОВИНИВ шаблонов при paGoi в с влек I ройной почтой ❖ 219
"strconv"
"text/template" ч- Использовать поддержку текстовых шаблонов
) для отправки обычных текстовых писем
type EmailMessage struct {
From, Subject, Body string Структура данных для представления
To []strin g электронного письма
var t *template.Template
func initO {
t = template.New("email")
t.Parse(emailTemplate)
func main() {
message := SEmailMessagei
From: ''me@exanple.com'',
Заполнение набора данных
To: []string)"vou@example.com"},
для шаблона иклиента
Subject: "A test",
электронной почты
Body: "Just saying hi",
}
var body bytes.Buffer Создание электронного письма по шаблону
t.Execute (&body, message) и запись его в буфер
authCreds := &EmailCredentials{
Username: "myUsername",
Password: "myPass",
Server: "smtp.example.com",
Port: 25,
} Настройка SMTP-клиента
auth := smtp.PlainAuth("",
authCreds.Username,
authCreds.Password,
authCreds.Server,
220 Приемы работы с шаблонами HTML и электронной почты
sntp.SendMail(authCreds.Server+":"+strconv.ltoa(authCreds.Pcrt), ◄ —i
a u th ' Отправка электронного письма I
ir.essage.From,
message.То,
body.Bytes ()) ч - Содержимое буфера передается методу отправки
в виде последовательности байтов
6.3. И тоги
Использование и расширение шаблонов для создания H TM L-раз-
метки или электронных писем позволяет справиться со сложностя
ми, труднопреодолимыми другими способами. Это становится осо
бенно заметно с увеличением сложности приложения. В этой главе
мы рассмотрели сценарии решения следующих задач:
О расширение функциональных возможностей шаблонов путем
добавления нестандартных команд;
О кэширование и буферизация шаблонов;
О определение разделов, совместно используемых несколькими
шаблонами. В H TM L-шаблонах к таким разделам относятся,
например, верхний или нижний колонтитул;
О создание базового, или мастер-шаблона, расширяемого други
ми шаблонами;
И ]oi и ❖ 221
Обслуживание
и получение
ресурсов и форм
Процесс приложения
Типичный сценарий, ------------
когда приложение выполняется
под управлением веб-сервера. Процесс приложения
Веб-сервер принимает Веб-сервер
запросы от пользователей
и передает данные между
пользователями и приложением Процесс приложения
Операционная система
Go-приложения сами принимают
запросы, используя встроенный
веб-сервер. Отпадает --------------
необходимость в наличии Пакет НТТР-сервера
промежуточного программного Go-приложение
package
обеспечения. Язык Go
сам управляет потоками Операционная система
выполнения
func main() {
http.HandieFunc ( , readme) ч - Регистрация обработчика для всех путей
http.ListenAndS>erve(":8080", n il)
http. Handle Func ( " /" , homePage) <- Обслуживание главной страницы, которая
h ttp . ListenAndServe!": 8080", n il) может подключать файлы из каталога static
http.ListenAndServe!":8080", pr)
func mainO {
Обслуживание статического содержимого 229
func main0 {
cache = make (map [string] *cacheFi le) ч- Создание карты файлов
http.HardleFuncserveFiles)
h ttp .T.istenAndServe(":8080", ni l )
Обслуживание ста i ическо! о содержимся о ❖ 231
i n f o , _ := f . S t a t ( )
v := & c a c h e F i l e {
co n te n t: r, Заполнить объект и сохранить в кэше
m o d T im e : i n f o . M o d T i m e ( ) , для дальнейшего использования
}
ca ch e [re q .U R L.P a th ] = v
Л и с т и н г 7 .7 ♦> В н е д р е н и е ф а й л о в в и с п о л н я е м ы й ф а й л с п о м о щ ью
g c.rice: e m b e d d e d _ file s .g o
package main
inport (
"github. com/Geert Johan/go. rice" <- Импорт пакета go.rice для обработки файлов
net/h. tp Метод HTTPBox открывает доступ к файлам,
реализуя интерфейс http.FileSystem
func main о { Определить местоположение
box := rice.MustFindBoxt". ./file s/") к- в файловой системе
httpbcx := box.HTTPBoxО ----------------------------------------------------------
http.ListenAndServe( " : 8080", hctp.FileServer(httpbox)) ---- 1
Запустить обслуживание файлов
1 Перевод на русский язык можно найти по адресу http s://b ag de r.gitb oo ks.
io/http2 -e xpla in e d/co nten t/ru/. - Прим. ред.
Обработка форм ❖ 239
формах могут содержать несколько значений. Эти два слу чан рассмат
риваются в следующих рецептах.
' 'h t n l^ Поле ввода c несколькими значениями, с именем "files'1и атрибутом multiple
Р еш е н и е
Для получения M IM E -типа файла можно использовать несколько
способов, обеспечивающих различную степень достоверности полу
ченного ответа:
О когда выполняется выгрузка файла, заголовки запросов со
держат поле Content-Туре, значение которого определяет тип
содержимого, например image/png таи универсальный тип
app li cat ion/o c te t-stre am ;
О расширение файла связано с типом M IM E, и по нему можно
выяснить тип загруженного файла;
О можно произвести разбор файла и определить тип на основа
нии содержимого.
О бс у ж д ен и е
Все три решения имеют разную степень достоверности. Значение поля
Content-Type устанавливается приложением после выгрузки, а расшире
ние файла определяется пользователем, выгружающим файл. Эти два
метода полагаются па аккуратность и достоверность определения типа
другой стороной. Третье решение требует анализа файла и умения опре
делять тип по содержимому. Это наиболее сложный метод, потребляю
щий больше системных ресурсов, но он самый надежный. Чтобы разо
браться в работе этих методов, рассмотрим каждый из них в отдельности.
Когда происходит выгрузка файлов, как мы видели в рецептах 45
и 46, становится доступным объект ym ultipart.FileH edder. Это второе
значение, возвращ аемое методом FornFile объекта Request. Объект
M u lt ip a r t.F il e H e a d e r имеет свойство Header, содержащее все поля за
груженных заголовков, включая поле Content-Type. Например:
fie, header, err := r.FormFi 1e ("file")
contentlype := header. Header ["Ccn-er.r-Type"J [OJ
Здесь метод FormFile вызывается для поля с именем file. Поля заго
ловка могут содержать несколько значений. В данном случае необхо
димо получить первое из них, даже при том, что имеется только одно
значение. Тип содержимого может быть конкретным M IM E -типом,
например image/png, или универсальным a p p l ic a tio n /o c te t- s tr e a m ,
когда тип неизвестен.
Альтернативой значению в заголовке служит расширение фай
ла, также определяющее его тип. Пакет mime содержит функцию
TypeEyFxtension, которая пытается вернуть M IM E -тип по расширению
файла. Например:
24В ❖ Обслуживание и получение ресурсов и форм
Буфер имеет размер всего 512 байт, потому что функция Detect-
Ccnter.tType анализирует только первые 512 байт содержимого фай
ла. Если попытка определить тип не увенчается успехом, она вернет
application/octet-stream .
Ограниченность списка типов содержимого, распознаваемых
функцией DeteetContentType, означает, что необходим другой метод
для определения некоторых распространенных типов файлов, таких
как документы M icrosoft Word, файлы M P4 и многих других распро
страненных форматов. Самый простой способ определения этих ф ор
матов - интеграция с внешней библиотекой, например libmagic. На
момент написания книги имелось несколько пакетов с поддержкой
библиотеки libmagic, упрощающих ее использование в программах на
языке Go.
ПРИМЕЧАНИЕ Описание порядка определения MIME-типов можно
найти на странице http://m im esniff .spec.whatwg.org/.
Обработка форм 249
Р еш ен и е
Вместо использования метода ParseMultipartForm будем читать со
ставные данные из запроса по мере их поступления. Это можно осу
ществить с помощью метода MuItipartReader объекта Request. По мере
поступления файлов или других данных, блок за блоком, будем со
хранять и обрабатывать их по частям, не дожидаясь завершения вы
грузки.
О бсуж дени е
Использование сервера в качестве транзитного пункта на пути дан
ных к конечной цели является общепринятой моделью. На практи
ке часто приходится сталкиваться с данными, которые не являются
файлами и хранятся в базе данных. Обработка больших файлов или
параллельная обработка множества файлов становится проблемой
при их кэшировании в локальных ресурсах па пути к конечной точ
ке. Проще всего передать решение этой проблемы конечному пункту
назначения, который должен быть в состоянии обеспечить хранение
больших файлов. Не стоит их кэшировать в локальной системе, если
на это нет особых причин.
Прием прямого доступа к потоку составных данных, который под
держивается методом ParseMultipartForm, заключается в использова
нии объекта Reader, возвращаемого методом MultipartReader объекта
Request. Получив этот объект, можно организовать обход в цикле бло
ков данных и читать их по мере поступления.
При обработке составных форм часто требуется обрабатывать ноля
ввода файлов наряду с текстовыми полями. Следующий листинг со
держит простую форму с текстовым полем ввода, полем ввода файла
и кнопку о тправки.
Л и с т и н г 7.16 ❖ H TM L-ф орма с полем ввода ф айла и текстовы м полем
cidoctype html>
<htnl>
<head>
< title> 7 i le Upload</title>
</head>
<body>
<fcrm action='7" method="FCST" encty?e=":nultipart/fonn-dat£">
clabel for="name">Name:</label>
Поле для ввода текста
cinput type="text" name="name" id="riaine">
<br> Поле для ввода файла,
<label for="file">Fi l e :</l abel > присутствие которого требует,
cinput type="file" name="iile" ic=":ile"> чтобы форма была составной
Обработка форм ❖ 251
<br>
<button type="subnit" name="submi t">Submit</buttcn> м ----------------
< / form> Кнопка отправки, также доступная как поле
</body>
</html>
maxValueBytes -= n
252 Обслуживание и получение ресурсов и форм
i f maxValueBytes == 0 ■
Использование счетчика байтов
msg := "multipart message too large"
гарантирует, что общий размер
fmt.Fprint(w, nsg)
текстовых полей не будет
return
слишком велик
}
Поместить содержимое values[name] = append(values[name]rb .S trin g ()
поля формы в карту conti nue
для последующего
использования
dst, err := os.Create("/tm p/" - filename)
-► defer d st.C lo se () Создать локальный файл
Закрыть файл i f err != n il { для сохранения содержимого
при выходе return
из обработчика )
for {
buffer := make I []byte, 100000}
cBytes, err := part.Read(buffer)
Записывать в файл
i f err == io.BOF {
содержимое по мере
break
выгрузки
}
d st.W rite(buffer[0 :cBytes])
)
}
fmt.Fprint(w, "Upload complete")
7.3. Итоги
Без обслуживания файлов и работы с формами не может обойтись ни
одно веб-приложение. Эти функции составляют основу Интернета
и применяются уже несколько десятилетий. В этой главе были рас
смотрены следующие методы, основанные на дополнительных воз
можностях языка Go:
254 Обслуживание и получение ресурсов и форм
Работа
с веб-службами
inport (
"fir.t"
" ic / ic u t il"
"net/http"
l/lc i ю льзование REST A P I ❖ 257
func main() {
res, _ :=
‘•h ttp .G e t("http://gcinpracticebook.com ") <- ВыполнениеGET-запроса
b, _ := io u til .ReadAll (res .Body) I Чтение тела ответа и закрытие объекта Body
res.Body.Close () | после завершения чтения
fm t.Frir.tf ("Is", b) ч - Вывод тела в поток стандартного вывода
}
I
errTxt := "use of closed network connection" ____________
i f err != n il && strin gs.C on tain s(err.E rro rO , errTxt) {
return true
I Превышение времени ожидания может быть выявлено
return fa lse дополнительной проверкой некоторых ошибок
Обсуждение
Серверы, подобные тому, что входит в стандартную библиотеку
Go, поддерживают работу с фрагментами файлов. Эту возможность
обеспечивают многие файл-серверы, а интерфейс определения диа
пазонов стал стандартным еще в 1999 году, когда выш ла специфика
ция HTTP 1.1:
func main О {
file, err := os.Create ("file, zip")
i f err != nil {
fmt.Println[err) Создание локального файла
return для сохранения загружаемых данных
defer file.Close()
location := https://example.eom/:ile.zip
err = download (location, file, ICO)
i f err != nil { Загрузка удаленного файла
в локальный файл с повторением
fmt.Println(err)
до 100 раз
return
I
fi, err := file. Stat ()
i f err != nil { Вывод размера
fmt.Println(err) файла после
return завершения
} загрузки
fmt.Frintf("Got it. with %v bytes downloaded", fi.Sized)
return err
) else i f err != n il ( Если ошибка связана с превышением времени
return err ожидания, повторить попытку загрузки
}
if res.StatusCode < 200 || res.StatusCode >= 300 Обработка
errFmt := "Unsuccess HTT? request. Status: yss" ошибочных
return fmt.Errorf(errFmr, res.Status) НТТР-кодов
\ состояний
if res.Header.Get("Accept-Ranges") != "bytes" { Если сервер не поддер
re trie s = 0 живает обработку файлов
) по частям, сбросить число
попыток в 0
err = io. Copy (file, res.Body) Копирование данных ответа в локальный файл
i f err != n il && hasTimedCut(err) {
i f re trie s > 0 {
return download(1 ocation, file, retries-1) ------------------
}
return err
} else i f err != n il ( Если ошибка превышения времени ожидания
return err возникла при копировании файла,
) попытаться получить оставшуюся часть
return r.il
func mainO {
http. Hand leFunc dispiayError) ч- Зарегистрировать dispiayError
http.TiistenAndServe (" :8080", n il) как обработчик всех запросов
264 ❖ Работа с веб-службами
"error": {
"code": 123,
"message": "An Error Cccurred"
Передела и оОраОо гка ошибок по 11ротоколу HTTP ❖ 267
Проблема
Как определить, что в ответе возвращена пользовательская ошиб
ка с определенной структурой, и как обработать ее надлежащим об
разом?
Решение
Получив ответ, проверим код HTTP-состояния и MIME-тип, что
бы определить факт ошибки. Если хотя бы один будет содержать не
ожиданное значение или сообщение об ошибке, преобразуем ответ
в ошибку, вернем и обработаем ошибку.
Обсуждение
Язык Go реализует принцип явной обработки ошибок и исполь
зует стандартные коды HTTP. Когда в ответ на HTTP-запрос воз
вращается неожиданный код состояния, его можно обработать как
прочие ошибки. Первый шаг - вернуть ошибку, если обработка
HTTP-запроса прошла не так, как ожидалось, как показано в следую
щем листинге.
Листинг 8.8 ❖ Преобразование HTTP-ответа в ошибку
type Error struct {
HTTPCode int json:'
Code int 1 son: "code,omirempty"'
Структурадля хранения
данныхоб ошибке
Message string ] son:"message"'
b, _ := ioutil.ReadAll(res.Body)
res.Bcdy.Close()
fm t.Frintf("Is", b)
»
tjp« br*on struct { Структура, представпяющая информацию в формате
я ш string 'j son: "name" JSON. Terjson отображает свойство Name в поле name
вданных в формате JSON
var JSON = '{
"name": "Miracle Max" Строка сданными в формате JSON
func main 0 {
var p Person <- Экземпляр структуры Person для сохранения данных в формате JSON
err := json.Unmarshal( []byte(JSON), &p) < ---------------------------------------
i f err != nil { Преобразование данных
fmt.Println(err) Обработка любых ошибок в формате JSON в экземпляр
return преобразования структуры Person
I
fmt. Prin tln (р) ч- Использование заполненного объекта Person,
в данном случае его вывод
Хотя стандартная библиотека предоставляет вес необходимое для
разбора данных в формате JS O N , однако вы можете столкнуться с до
статочно распространенными ситуациями, не имеющими очевидного
решения.
Разбор и отображение данных ы формате JSON ❖ 271
" fir.t"
"os"
fm t.Println(f) Д
default:
fmt.Println("Unknown type")
)
https://example.com/api/v1/todos
^ ------- / ^ ^ 4
П реф икс U RL-адреса Версия Путь к конкретном у API
inport (
"encoding/json"
" fir.t"
"net/http"
)
tvpe testMessage struct n .
„ l . .. „ Пример функции,
Message string -son:"message"
возвращающей ответ
в формате JSON
func displayTest(w http.ResponseWriter, r *http.Request) { -------------
data := testMessage{"A test message."}
b, err := json.Marshal(data)
i f err != n il {
http.Error(w, "Internal Server Error", 500)
return
w.Header().Set("Content-Type", "application/json")
fmt.Fprint(w, string(b)) n . n.
Включение версии API
в путь, который отображается
func main О { в функцию-обработчик
http.Har.dleFunc("/api/vl/test", displayTest) < ----------------------
http.ListenAndServe(":8080", nil)
РЕЦЕПТ 55 В ерси и п р и кл а д н ы х п р о гр а м м н ы х и н те р ф е й со в
в типе со д е р ж и м о го
Предыдущий рецепт описывает метод, облегчающий жизнь разработ
чикам, но он не лишен семантических недостатков. Согласно ориги
нальной теории REST, URL-адрес должен предоставлять что-то. Это
может быть объект, список или что-то еще. В зависимости от деталей
запроса, например типа запрашиваемого содержимого или НТТР-
метода, этот объект будет отвечать или действовать по-разному.
Проблема
Как всрсионироватъ прикладной программный интерфейс более
семантическим способом?
Р еш ен и е
В запросе и ответе вместо ссылки на JSON используем нестан
дартный тип содержимого, включающий номер версии. Например,
вместо application/j son используем, например, тип application/vnd.
mytodo.vl.json или application/vnd.mytodo. json;version=1.0. Такие не
стандартные типы определяют схему данных.
Обсуждение
Для доступа к разным версиям APT с использованием одного пути,
помимо всего прочего, необходимо учиты вать тип содержимого, как
показано на рис. 8.2. Листинг 8.13 демонстрирует один из методов
определения типа содержимого и использования его при конструи
ровании ответа.
am glexom /agi/vl/todos
Путь кAPI с номером j
версии в URL J
П реф икс U R L-ад реса В ерсия П уть к конкретн ом у API
b, _ := ioutil.ReadAll(res.Body)
res.Body.C lose() Вывод тела ответа
fn t.P rin t"("% s", b)
8.5. И тоги
Эта глава началась со знакомства с основами использования веб
служб, такими как выполнение R E ST -запросов. Затем мы быстро
перешли к обсуждению приемов, обеспечивающих надежные взаи
модействия с веб-службами, в том числе:
О определение превышения времени ожидания в сети, даже если
на уровне сети отг формально но указан, и возобновление за
грузки по истечении времени ожидания;
О передача ошибок между конечными точками API и клиентами
е помощью и без помощи НТТР-яаголовков;
О разбор JSO N -данных неизвестной структуры;
О использование двух методов версионирования R E ST API
и работа с прикладными программными интерфейсами раз
ных версий.
280 Работа с веб-службами
РАЗМ ЕШ ЕН И Е
ПРИЛОЖ ЕНИЙ
В О БЛ АКЕ
Использование облака
Традиционный SaaS:
сервер laaS: PaaS: Программное
или стойка Инфраструктура Платформа обеспечение
серверов как услуга как услуга как услуга
52
Приложение Приложение ¥.1 Приложение Приложение
Контролируется
.Я
разработчиком
Программное обеспечение
как услуга (SaaS). Может
запускаться в laaS или Программное обеспечение как услуга
PaaS. Может использо- (например, виртуальные машины)
ваться приложениями
и пользователями ___________________________________________
Приложение А2
Приложение В
библиотеки
библиотеки
<u i
Гостевая Двоичные
Гостевая Двоичные
3 о;
|Е
ш5
=15
Гостевая
ОС
ОС
ОС
Гипервизор
ОС хоста
Сервер
РЕЦЕПТ 56 Работа с н е с ко л ь ки м и о б л а ч н ы м и п р ов а й д е р а м и
fm t.Println("Storing content..." )
err = store.Save("foo/bar", body)
i f err != n il (
Сохранение примера содержимого в файл
fmt.Println(err)
o s.E x it(1)
\
fmt.Println("Retrieving content..." )
c, err := store.Load("foo/bar” )
i f err != n il (
fmt.Println(err)
o s.E x it(1)
}
Чтение и вывод содержимого из файла
o, err := ioutil.ReadAll(c)
i f err != n il {
fmt.Println(err)
o s.E x it(1)
Проблема
Как выводить сообщения или еще как-то реагировать на возникно
вение ошибок в методах взаимозаменяемых реализаций интерфейса?
Реш ение
I e l s e i f e r r != n i l [
l o g . P r i n t f [ " E r r o r loading file %s, e r r : %s", path, err)
oerr = ErrCannotLoadFile
I
r e tu r n o, o e rr ч- Возврат ошибки, если она Обработка ошибок,
имеется, вместе с файлом не связанных с отсутствием файла
Р Е Ц Е П Т 59 Выявление зависимостей
Помимо взаимодействия с ядром и базовой операционной системой,
Go-приложения могут обращаться к другим приложениям. Обычно
для этой цели используется пакет os/exec из стандартной библиоте
ки. Но что произойдет, если попытаться вызвать приложение, отсут
ствующее в системе? Предположение о существовании зависимостей
может привести к непредвиденным ситуациям, а неспособность обна
руживать подобные проблемы усложнит диагностирование ошибок.
Проблема
Как проверить доступность внешнего приложения до его вызова?
Реш ен и е
Перед вызовом внешнего приложения удостоверимся, что оно
установлено и готово к использованию. Если приложение отсутству
ет, запишем в журнал ошибку, что поможет в устранении неполадок.
Обсуж дение
Выше уже говорилось, что Go-приложения могут выполняться
в различных операционных системах. Например, приложение, ском
пилированное для Linux, можно запустить в различных дистрибути
вах с разными установленными приложениями. Если приложение
полагается на другое приложение, последнее может присутствовать
или отсутствовать в системе. Ситуация еще больше усложняется
в облаке, где может использоваться большое количество дистрибу
тивов. Некоторые специализированные дистрибутивы Linux для об
лака имеют порой сильно ограниченный набор команд.
Всякий раз, когда облачное приложение полагается на другое при
ложение, оно должно проверить его присутствие и зафиксировать
факт отсутствия требуемой зависимости. Это легко можно сделать
с помощью пакета os /ехес. Следующий листинг демонстрирует функ
цию, выполняющую такую проверку.
Л и с т и н г 9.7 ❖ Функция для проверки доступности приложения
func checkDep(name string) error {
if err := exec.LookPath(name); err != n i l •
es := "Could not find ' %s' in PATH: %s" Проверка наличия
return f.nt.Errorf(es, name, err) зависимости в одном
} Возврат ошибки из путем в PATH.
return n il <- Возврат значения nil ПрИ отсутствии При отсутствии
при отсутствии ошибок зависимости генерируется ошибка.
Выполнение на облачных серверах 299
РЕЦЕПТ 60 К р о сс-пл а тф о р м е н н а я ко м п и л я ц и я
Помимо различных облачных окружений, часто бывает так, что при
ложение разрабатывается в Microsoft Windows или Apple OS X, а экс
плуатируется в Linux или распространяется через облако в версиях
для Windows, O S X и Linux. Очень часто приложение разрабатыва
ется в одной операционной системе, а эксплуатироваться должно
в другой.
Проблема
Как скомпилировать приложение для архитектур и операционных
систем, отличных от тех, на которых выполняется компиляция?
Реш ение
func main 0 {
до ir.onitorRuntime () <- В момент запуска приложения начать мониторинг
i := О
for i < 40 {
gc func0 {
time. Sleep (15 * time. Second) Создание примеров сопрограмм для имитации
} () использования памяти в течение 40 секунд
i = i + 1
tim e.S leep (1 * time.Second)
I
9.4. Итоги
Облачные вычисления становятся все более популярными, и язык Go
активно их поддерживает. Эта глава была посвящена теме использо
вания языка Go в облаке. В то время как предыдущие главы лишь ка
сались облачных вычислений, эта глава охватила все аспекты успеш
ного использования Go-приложений в облаке, в том числе:
О работу с различными поставщиками облачных услуг и при
емы, позволяющие избежать зависимости от конкретного по
ставщика;
О сбор информации о хосте вместо выдвижения предположений
о нем;
О компиляцию приложений для различных операционных си
стем и приемы, позволяющие избежать зависимости от кон
кретной операционной системы;
О мониторинг среды выполнения Go для выявления причин воз
никающих проблем и получения подробных сведений о работе
приложения.
Следующая глава будет посвящена взаимодействию облачных
служб с применением технологий, не связанных с R EST API.
Глава
Взаимодействие
облачных служб
10.1. Микрослужбы
и высокая доступность
Приложения, основанные на архитектуре микрослужб, создаются как
коллекции служб, развертываемых независимо друг от друга. Увели
чение сложности систем, требование независимого масштабирова
ния отдельных частей приложений и необходимость повышения на
дежности и отказоустойчивости приложений привели к появлению
микрослужб. Примерами микрослужб могут служить приложения
управления конфигурациями, например eted, или перекодировки ме
диафайлов из одного формата в другой. Обычно микрослужбы обла
дают следующими характеристиками:
О выполняют одно действие. Например, хранят конфигурацию
или преобразуют файлы из одного формата в другой;
О обеспечивают гибкость и поддерживают горизонтальное мас
штабирование. При изменении нагрузки на микрослужбу ее
можно масштабировать вверх или вниз;
О устойчивость к сбоям и ошибкам. Службу можно развернуть
так, что она будет работать безотказно, даже когда происходят
сбои в отдельных якземплярах.
Это вполне согласуется с философией UNIX: делать что-то одно,
но делать это хорошо.
Предположим, что нужно разработать службу для преобразования
медиафайлов из одного формата в другой. Пользователь выгружает
медиафайл, он перекодируется, и медиафайл в новом формате пред
лагается для загрузки. Это можно реализовать как одно монолитное
приложение, содержащее все необходимые компоненты, или в виде
набора микрослужб с различными функциями, каждая из которых
будет отдельным приложением.
Схема приложения перекодировки, построенного в виде комплек
са микрослужб, показана на рис. 10.1.
308 * Взаимодействие облачных служб
Время
- - - - - - - - - г - - - - - - - - - - - - - К- - - - - - - - - - - - - - К- - - - - - - - - - - - - - \ - - - - - - С е р в е р
Каждый запрос использует \ \ \ \
отдельное соединение \ \ \ \
------- Д-Н^--------Д-1-Н ---------Д-Н-/-------- Д-Ь Клиент
0пфЫ1ие 0ифЫ1И^ Заьры ие O utline З а ш м й Скрыне Зац.ы!ис Заиры не
Сервер
Все запросы используют
одно и то же соединение
Клиент
Оирыгие Закрытие
Время
Сервер
В к о н ве й ер е за п р о сы
п е р е д а ю тс я п а р а л л е л ь н о
//л\\
/Ж Г
/ / / kka Клиент
Огфыгие Закрыли
А Г \ Сервер
А А А /\
/ \\ / / \v/ \V/ у
П о сл е д о в а т е л ь н а я
п е р е д а ч а за п р о со в ,
о дн ого за д р уги м
,/ Клиент
Открытие Закрытие
defer r . Body. C lose () <- Отложенное закрытие тела при выходе из функции main()
о, err := icutil.R eadA il(г.Body)
i f err != nil {
defer r2.Body.Close()
о, err = icutil.ReadAil(r2.Body)
i f err != n il {
)
о, err := io u til.R e a d A ll(г .Body)
i f err != n il { Копирование тела ответа
в другой экземпляр
) с последующим его закрытием
r.Body.Close() -<■
// Использовать содержимое тела
r2, err := http.Get("h ttp ://example.com/foo") ч- Выполнение другого
i f err != n il { HTTP-запроса. Этот запрос
повторно использует
} соединение, созданное
о, err = ioutil.ReadAll (r2 .Body) для предыдущего запроса
i f err != nil {
}
~2.Body.Close О
defer res.Body.Close О
b, err = icutil.ReadAll (res. Body) «- Чтение тела ответа для получения одного полного
i f err != nil { сообщения в формате Protocol Buffers
Проблема
Как организовать взаимодействия через R P C способом, не завися
щим от используемых языков программирования?
Реш ение
func main() {
1, err := net.Listen("top", ":55555") Запуск TCP-сервера, прослушивающего
i f err != nil ( порт 55555 и обрабатывающего все
ошибки. Его будет использовать
) gRPC-сервер
s := grpc.NewServer()
pb.RegisterHelloServer(s, 6server{}) Создание gRPC-сервера и обработка
-► s .Serve(1) запросов через ТСР-соединение
1
defer conn.Close()
c := pb.NewHelloClient(conn) <-------------
nair.e := "Inigo Montoya" _____ Создание нового экземпляра
hr := &pb.HelloRequest[Name: name) клиента службы hello,
r, err := c.Say(context.Background!), hr) определенной в файле
i f err != n il ( Protocol Buffers из листин
га 10.11. Взаимодействие
} происходит через созданное
fm t.Frintln(r.M essage) -*(— ранее соединение
Вывод ответа, сгенерированного
другой службой
Подготовка сообщения
Передача контекста и запроса для Say HelloRequest для передачи
с получением ответа и ошибки. Обратите внимание, в другую службу
что интерфейс Say совпадает с интерфейсом
сервера из листинга 10.12. Обработка всех ошибок,
которые могут возникнуть
10.3. И тоги
В этой главе мы рассмотрели приемы организации взаимодействий
через REST APT и более быстрые альтернативы. Подобные альтерна
тивы широко используются в Google, Facebook и в других компани
ях, применяющих множество отдельных служб, взаимодействующих
между собой. Здесь мы охватили следующие темы:
О взаимодействие микрослужб и почему оно может стать проб
лемой;
О повторное использование соединений для повышения произ
водительности за счет исключения из рабочего процесса ме
ханизмов замедления ускорения передачи в протоколе TCP,
управления нагрузкой и согласования соединений;
О ускорение маршалинга и демаршалинга J SON-данных за счет
исключения затрат на рефлексию;
О использование формата Protocol Buffers вместо JSO N при об
мене сообщениями;
О взаимодействие через RPC с помощью gRPC.
Следующая глава знакомит с использованием механизма рефлек
сии и метапрограммирования в языке Go. В ней мы посмотрим, как
генерировать код и использовать теги в структурах.
Глава
Рефлексия
и генерация кола
Тип
import (
" fir.t"
"streenv"
)
func main() {
var a uir.tS = 2
var fc int = 3 7
Три области применении рефлексии ❖ 331
return res
import (
"fmt"
"reflect"
"strconv"
)
type Mylnt in t 64
func main() {
/1/1...
var a uint8 = 2
var b int = 37
var c strin g = "3 .2 "
var d Mylnt = 1 reflect.Kind - это обычный тип,
res := sum (a, b, c, d) поэтому во фразах case можно
fm t.F rin tf("R e su lt: %f\n", res) использовать несколько значений
default:
fmt.Printf("Unsupported type %T. ignoring.\n", val)
return res
inport (
"bytes"
"fir .t"
func isS trin g er (v in te rfa c e d ) bool { Получение значения типа interface^ и выполне-
ck := v. (fm t.Stringer) ние проверки соответствия нужному интерфейсу
return ok
inport (
" fir . t"
"ic"
Три области применения рефлексии 337
"reflect"
func main О {
n := SNarae{First: "Inigo", Lasr: "Montoya"} < ---
stringer := (*fmt.stringer) (nil) <- Создание указателя на nil типа fmt.Stringer
implements (n, stringer) <- Проверка поддержки интерфейса значением n
fmtStringer (содержит метод StringO)
writer := (*io.writer) (nil) Создание указателя nil типа io.Writer
implements (n, writer) <- Проверка поддержки интерфейса значением n
io.Writer (содержит метод Write())
import (
" f ir.t"
"reflect"
"strin gs"
)
type Mylnt ir.t
type Mylnt in t
inport (
"encoding./j son"
"fmt"
tunc mainO {
n := &Name{"Inigo", "Montoya"}
data, _ := json.Marshal(n) Преобразование п в JSON
fm t.F rin tf("%s\n", data)В и вывод результата
{
"firs t": "Inigo ",
"la s t": "M ontoya"
}
type Person struct { ^
FirstName string json:"first” xml:"firstName,attr"'
LastName string json:"last" xm!:"lastName"' \
} 4
4 <Person firstN am e="lnigo">
<LastName>Montoya</LastName>
</Person>
Р еш ен и е
Определим формат аннотаций (предпочтительнее с использова
нием синтаксиса тегов, описанного выше). Затем используем пакет
reflect для создания инструмента, извлекающего сведения из анно
таций.
О бсуж дени е
Допустим, что нужно написать кодировщик для файла с простым
синтаксисом в виде набора пар имя-значение. Этот формат напоми
нает старый формат INI. Пример такого файла выглядит следующим
образом:
total=247
runninq=2
sleeping=245
threads=1189
load=70.87
func main() {
fmt.Frintln("W rite a struct to output:")
prcc := &Processes!
Total: 23,
Running: 3,
Sleeping: 20, Создание экземпляра структуры Processes
Threads: 34,
Load: 1.8,
data, err := Marshal (proc) <- Маршалинг структуры в массив байтов Gbyte
i f err != n il 1
panic(err)
t := v a l. T y p e O
for i := 0; i < t.N un F ield() ; i++ { к - Обход всех полей структуры
f := t .F ie ld ( i)
name := fieldMane(f) <- Получение имени из tagName
raw := v a l.F ie ld ( i) .I n t e r f a c e ()
\
fm t.Fprin tf(Sb, "%s=%v\n", name, raw) •<-----------------------------------------
return ь . B y t e s ( ) , n il « - Возврат содержимого буфера
Использование форматирования для вывода
необработанных данных в буфер
}
se tF ie ld ( p a ir [0], p a i r [1], t, val) •<
I
return r.il Установка значения с помощью функции setFieldQ
i continue
}
dest = reflect. ValueOf (fvai)
-------------------- ► case reflect.Strin g:
dest = reflect.ValueOf(value)
-------------------- ► case reflect.Eool:
bval, err := strconv.ParseBool(value)
i f err != n il {
fmt.Printf(
"Could not convert %q tc bool: ls\n ",
value, err)
continue
(
dest = reflect.ValueOf(bval)
)
v .F ie ld (i) .Set (dest) «- Запись значения в соответствующее
} поле структуры
}
ty p e M yTypeQ ueue s tr u c t {
q []M yType Простая очередь типизированных элементов
f u n c N e w M y T y p e Q u e u e () * M v T y p e Q u e u e {
re tu rn SM yTypeQ ueue{
q : [] M y T y p e d ,
\
f u n c N e w { { . МуТуре}}Q u e u e {) * { { . М у Т у р е } } Q u e u e {
re tu rn &{ { .М уТ уре}}Q ueue{
q: [ ] { { .М уТуре}}{},
I
import "fmt"
func main() {
var one, two, three Mvint = 1 , 2, 3
q := NewMylntQueue()
q. Insert(one)
q.Insert(tw o) Использование MylntQueue
q .In sert(th ree)
11.4. И тоги
Эта глава была посвящена более сложным темам: рефлексии и мета-
программированию. В процессе знакомства с рефлексией вы увиде
ли, как использовать типы, значения, виды, поля и теги для решения
различных задач программирования. При обсуждении мстапрограм-
мирования было показано, как с помощью инструмента go generate
писать код, создающий другой код.
Эта глава знакомила со следующими возможностями:
О использование видов для получения важных сведений о типах;
О определение реализации типом заданного интерфейса во время
выполнения;
О доступ к полям структуры во время выполнения;
О работа с аннотациями;
О анализ тегов r аннотациях структур;
О написание функций для маршалинга и демаршалинга;
О использование инструмента go generate;
О написание шаблонов Go, генерирующих Go-код.
362 *> Рефлексия и генерация кола
G А л г о р и т м S e c u re H a s h A lg o r it h m
G o -п од п р огр ам м ы , 93 (S H A ), 28
обзор, 28 А л г о р и т м T r a n s p o r t L a y e r S e c u r it y
(T L S ), 28
н А л г о р и т м T r ip le D a t a E n c r y p t io n
H T M L ш аблоны , 197 A lg o r it h m ( T D E A ) , 2 8
прерывание вы полнения А л гор и тм ш иф рования A dvanced
ш аблонов, 204 E n c r y p t io n S t a n d a r d ( A E S ) , 2 8
си н та кси ч е ск и й разбор, 20 3 Анализ ош ибки, 264
соединение ш аблонов, 207 Аннотации
стандартная библиотека, 197 проверка допустим ости, 346
ти п H T M L , 199 структуры , 344
H T T P -за п р о с G E T , 2 5 2 теговы е. 3 4 5
Н Т Т Р -к и е н т А н о н и м н ы е g o -п о д п р о гр а м м ы , 9 5
ген ерац и я п о л ьзо вате л ьски х А с и н х р о н н ы й в в о д -в ы в о д , 4 3
ош ибок, 264 А т р и б у т m u lt ip le , 2 4 3
обзор, 26
передача и обработка ош ибок, 263 Б
п ри кл адн о й п рогр ам м н ы й Б и б л и о т е к а lib m a g ic , 2 4 8
интерф ейс R ES T , 256 Б и б л и о т е к а д л я в е б -п р и л о ж е н и й
счи ты вание и использование S in a t r a , 9 0
п ол ьзо вательских ош ибок, 267 Б л о к d e fe r, 1 6 3
Н Т Т Р -м е то д ы , 79 Б л о к и try /c a tc h , 1 3 0 ,1 3 4
Б л оки р овка посредством
I каналов, 120
I P -а д р е с х о ста , 2 9 7 Б л оки р овки . 103
Боты , 288
А Б у ф е ] ) b y t e s . B u lle r , 1 9 4
А варии, 134
восстановление, 139 В
и соп р огр ам м ы , 145 В е б - и н с т р у м е н т G o r illa , 9 0
обзор, 134 В е б -се р в е р ы , р а б о та , 7 3
отличие от ош ибок, 135 зап уск и остановка, 74
работа с авариям и, 136 обзор, 73
А л г о р и т м D a t a E n c r y p t io n S t a n d a r d перенаправлени е запросов, 79
(D E S ), 28 В е б -сл у ж б ы , раб ота, 2 5 5
А л го р и т м K e y e d -H a sh M essage ген ер ац и я п о л ьзо в а те л ьски х
A u t h e n t ic a t io n C o d e ( H M A C ) , 2 8 ош и б ок,264
364 Предметный указатель
f la g . P a r s e ( ) , 5 5 н
F o r m F ile , 2 4 1 Н аб о р и н стр ум е н то в go, 299
F o n iiN a in e , 2 5 3 Н аб о р и н стр ум е н то в я зы ка G o, 32,
F o r m V a lu e , 2 3 8 302
G e t B o o l, 6 9 охват кода, 36
g e tN a m e , 3 5 тести рован ие, 34
in s e r t , 3 5 6 управление пакетам и, 33
Load, 293 ф орм атирование, 37
m a r s h a l( ), 3 4 8 , 3 5 0 Н атуральны е облачны е прилож ения
M u lt ip a r t R e a d e r , 2 4 9 , 2 5 2 и контейнеры , 286
in u t e x .R T .o r k , 2 3 2 Н сбуф сри зовап н ы с каналы , 119
m u t e x . R T J n lo c k , 2 3 2 Н еобработанны е составны е
n e t . D ia l, 1 6 3 данны е, 249
n e t .D ia lT im e o u t , 1 6 7 Н есколько облачны х
o s .G c t p id ( ) , 2 9 6 поставщ иков, 289
o s .G c tw d (), 2 9 7
o s .H o s tn a m e (), 2 9 6
О
О блачны е вы числения, 282
P a rse F o rm . 2 3 9
и зб еган и е п р и в я зки к о б л а ч н ы м
P a r s e M u lt ip a r t F o r m , 2 3 9 , 2 4 6 , 2 4 9 ,
провайдерам, 289
252
инф раструктура как служ ба, 284
P o s t F o r m V a lu e , 2 3 9
кон тейнеры и натуральны е
P r o c e s s M u lt ip a r t F o r m , 2 4 1
облачные прилож ения, 286
r e f le c t .I n d ir e c t Q . 3 4 2
м о н и то р и н г среды
re m o v e , 3 5 6 вы полнения, 302
R Lock, 232
обзор, 38
R U n lo c k , 2 3 2
обработка ош ибок, 293
R u n P a r a lle l. 1 9 0 платф орм а как служ ба, 284
Save. 293 п о л уч е н и е св е д е н и й о среде
S e rv e H T T P , 85, 88 вы полнения, 295
S t r in g ( ) , 3 3 5 сб ор ка д л я облака, 299
S t r ip P r e f ix , 2 2 6 управление облачны м и
T im e o u t ( ) , 2 5 8 служ бам и, 288
T v p e . E le m ( ) , 3 3 8 О блачны е служ бы
T y p e .In te rfa c e d ), 3 3 8 алтернативы R E S R , 317
u n m a r s h a l( ) , 3 4 8 м икрослуж бы , 307
w o rd s. L o c k , 108 ускорение R E S T , 309
w o r d s .U n lo c k , 1 0 8 О б н аруж ен и е кон кур ен ц и и , 106
М и кросл уж бы , 288, 307 О бработка ош ибок, 125
М одель взаим одействую щ их О бработка ф орм, 238
п ослед овательн ы х процессов запросы ф орм , 238
(C S P ), 93 работа с необработанны м и
М о д у л ь н о е т е с т и р о в а н и е , 17 7 составны м и данны м и, 249
М о н и то р и н г среды вы п олн ен и я работа с ф ай л ам и и предоставление
и облачны е вы числения, 305 со ставн ы х дан н ы х, 241
Предметный указа Iель ❖ 367
lo g , 1 5 7 , 1 6 3 соединений, 309
m a n n e rs , 7 7 П овтор реализации блокировок, 103
m im e , 2 4 7 П одтверж дение А С К , 165
n e t, 2 6 П о л е C o n le n l- T y p e , 2 4 7
n e t/ h ttp , 1 5 1 ,3 1 0 П о л е O llie r , 3 4 5
n e t / h t t p / c g i, 2 2 4 П о л уч е н и е св ед ен и й о среде
n e t / h t t p / f a s t c g i, 2 2 4 вы полнения при облачны х
n e t/s m tp , 2 1 8 вы числениях, 295
n e t / u r l, 8 2 П ользовательские ош ибки
o s/e x e c, 2 9 8 генерац ия, 26 4
p a th , 8 3 , 8 6 передача, 26 4
r e f le c t , 5 8 , 3 1 4 , 3 2 9 , 3 3 3 , 3 4 3 , 3 4 7 счи ты вание и использование, 267
re g e x p , 8 9 П о л я стр у к тур ы , 351
r u n t im e , 1 7 4 , 3 0 4 П орож д аю щ ее тестирование, 183
s ig n a l, 7 8 П о то ко ва я передача ответов, 205
sm tp , 2 1 8 П ревы ш ение времени
s trc o n v , 7 3 ож идания, 258
s t r in g s . 8 2 П редоставление составны х данны х
s y n c , 7 7 , 102, 108, 1 1 9 ,2 3 2 и работа с ф ай л ам и , 241
s y s lo g , 1 5 7 П р и кл ад н о й п рогр ам м н ы й
t e s t in g , 3 5 , 1 8 6 интерф ейс R E S T
t e s t in g / q u ic k , 1 8 4 альтернативы , 3 1 7
t e x t / t e m p la t e , 1 9 7 , 2 2 0 обзор, 26
t e x t / r e m p la t e / p a r s e r , 2 0 3 ускорение, 309
y a m l, 6 9 П р и л о ж е н и е H e llo G o , у с т а н о в к а , 4 5
ф орм атирования, 33 П рилож ение перекодировки, 307
П араллельная обработка П рилож ения, учиты ваю щ ие
каналы , 108 двенадцать ф акторов, 72
обзор, 28 П р о г р а м м а s im p le _ g z . g o , 9 9
П араллельны е вы числения П р о гр ам м н о е обеспечение
соп р огр ам м ы , 93 как служ ба, 285
П еременная, 329 П р о е к т K u b c rn c tc s , 361
c o n f, 6 8 П р о т о к о л T r a n s m is s io n C o n t r o l
e rr, 1 4 4 P ro to c o l ( T C P ) , 26
f i le , 1 4 4 П р о т о к о л U s e r D a ta g ra m P ro to c o l
PO R T , 72 (U D P ), 26
П ерем ен н ая среды
S G O P A C K A G E , 359 P
S G O P A T H , 46 Работа
G O A R C H , 300 со ста ти ч ески м кон тен том . 223
G O O S , 300 с пакетам и, 33
П ер е м е н н ы е среды , 4 7 с ф айлам и и предоставление
П л а т ф о р м а N o d e .js , 4 3 со ставн ы х дан н ы х, 241
П овторное использование Разбор дан н ы х в ф орм ате JS O N . 270
Предме I ный yKddd 1ель ❖ 369
E x t e n s ib le M a r k u p L a n g u a g e co u n t, 3 0
(X M L ), 28 c tx .E rr(), 3 2 6
JS O N d a t e F o r u ia l, 2 0 2
д ем а р ш ал и н г, 3 1 4 D e te c tC o n te u tT y p e , 2 4 8
м арш али н г, 3 1 4 D ia l, 2 6
о б зо р , 2 8 ,6 6 d iv id e , 1 3 6
разбор и отображ ение, 270 d o w n lo a d , 2 6 1
M essageP ack, 3 1 5 E le m ( ) , 3 3 8
P r o to c o l B u ffe rs E rro r, 8 2 ,1 3 0 , 2 2 8 ,2 6 4
взаим одействие, 321 e rro rs .N e w , 1 2 8 ,1 4 2
з а п р о с к g R P C -се р в е р у , 3 2 4 f ie ld N a m e ( ) , 3 5 0
клиент, 320 f ile p a t . h . J o in , 3 0 2
н а стр о й к а сервера, 3 1 9 f ile p a t . h . S p lit . , 3 0 2
обзор, 3 1 7 f ile p a t h . S p lit L is t , 3 0 2
о б р аб о тч и к д л я сервера, 3 2 0 f i lc p a t h . T o S l a s h , 3 0 2
определение сообщ ений f ile s t o r e , 2 9 2
и R P C -в ы зо в о в , 3 2 2 f lo a t 6 4 , 3 3 0
ф айл, 318 fm t.E rro rf, 1 2 8
Y A M L ( Y A M L A i n ’t M a r k u p f m t . F p r in t f ( ) , 3 5 0
L a n g u a g e ), 6 8 f m t . S p r in t f , 2 0 0
Ф орм атирование, 37 f s . F il e S e r v e r , 2 2 9
Ф р е й м в о р к c li.g o , 6 2 goodbye, 82
Ф р е й м в о р ка ком андной строки, 59 h a n d le , 1 4 6 , 1 4 9
ком анды и подкоманды , 62 h a n d le r , 7 8
простое кон сол ьн ое hom ePage, 82
прилож ение, 60 h ttp , 2 0 3 , 251
Ф ункции h t t p . F ile S e r v e r , 2 2 9
P r in t S t a c k , 1 7 4 h ttp .G e t() , 31 1
Т .Е г г о г , 1 7 8 h t t p . H a n d le F u n c , 4 8
Т . F a t a l, 1 7 8 h ttp .IT e a d , 2 5 6
Ф ун кц и о н ал ьн ы й набор h tt p .P o s t Q , 2 5 6 ,3 1 1
инструм ентов h ttp .P o s tF o rm , 2 5 6
A n s ib le , 6 6 im p le m e n t s ( ) , 3 3 8
C h e f, 66 lis t e n , 2 6 , 1 4 6 , 1 4 9
P u p p e t, 66 L is t e n A n d S e r v e , 7 7
Ф ункция L o g f(), 191
A c t io n , 6 2 lo g . F a t a l , 1 6 3
b a r, 1 7 3 lo g .F a t a lln , 1 6 3
C a lle r , 1 7 6 lo g . P a n ic , 1 5 8
C a lle r s , 1 7 6 lo g .P r in t f , 1 5 8
c a n c e l( ) , 3 2 5 m a in , 3 0 , 4 8 , 5 9 , 6 7 , 8 5 , 9 5 , 9 6 , 1 0 5 ,
C h e ck (), 185 118, 139, 1 4 4, 1 4 5 ,2 1 7
c li. N e w E x it E r r o r , 6 2 m essage, 153
C o n c a t, 12 6 N am es, 24
372 Предметный указатель
O p e n C S V , 144 w g .D o n e , 101
p a n ic ( n il) , 1 3 7 w g . W a it , 1 0 1
P a rse , 5 6 , 2 0 3 w o rd s .a d d , 1 0 6
P a r s e F ile s , 2 0 3 , 2 0 9 y ik e s , 1 4 1
P a r s e ln t , 7 3 заверш ения, 116
p r e c h e c k D iv id e , 1 3 6
p r in t C o u n t , 3 2 X
P r in t D e f a u lt s , 5 5 Х р а н е н и е ф ай л ов, 291
p r in t f , 2 0 0 Х р а н е н и е ф ай л о в в облаке, 291
R e a d F ile , 6 9
R e a d F ile T n t o , 7 1
ц
Ц и к л fo r, 3 2 , 10 1
r e a d S t d in , 1 1 2
Ц и к л f o r / s e le c t , 1 1 7
re c o v e r, 1 4 0
r e f le c t . T y p e O f ( ) , 3 3 8 ч
r e f le c t . V a lu c O f ( ) , 3 4 2 Ч тени е пол ьзовательских
R c m o v c E m p t y L in e s , 1 4 3 ош ибок, 267
re sp o n se , 1 4 6 , 1 4 9
r ic e .M u s t F in d B o x , 2 3 4 ш
ru n , 145 Ш аблон
r u n t im e . G o s c h e d ( ) , 9 6 h e a d . h t m l, 2 0 8
r u n t im e . R e a d M e m S t a t s , 3 0 4 p a g e . h t m l, 2 1 3
S e n d R e q u e s t, 1 3 3 u s e r . h t m l, 2 1 3
S e rv e C o n te n t, 2 2 8 , 2 3 2 очереди, 357
s e r v e T e m p la t e , 2 0 2 Ш аблоны , 196
s e t F ie ld ( ) , 3 5 2 H T M L -ш а б л о н ы , 19 7
s ta rt, 1 4 5 использование для электронной
S t r u c t F ie ld . T a g ( ) , 3 5 0 почты , 218
S t r u c t F ie ld . T a g . G e t ( ) , 3 5 0 обзор, 196
su m (), 3 3 3 преры вание вы полнения, 204
s y n c .M u te x , 102, 1 9 4 си н та кси ч е ск и й разбор, 203
s y n c .R W L o c k , 108 соединение ш аблонов, 207
s y n c . W a it G r o u p , 9 7 стандартная библиотека, 197
s y s lo g . D e b u g , 1 7 3
э
s y s lo g . D ia l, 1 7 2
Э к з е м п л я р t e m p la t e . T e m p la t e , 2 0 2
T e stN a m e , 3 5
Э л е ктр о н н а я почта, и сп о л ьзован и е
t im e . A f t e r , 1 1 2 ш аблонов, 218
t im e . S le e p , 9 7
t im c . T im c , 1 1 2 Я
T v p c B y E x t e n s io n , 2 4 7 Я зы к С , 38
T y p c . N u m F ic ld ( ) , 3 4 2 Я з ы к Р Н Р , 41
U n m a r s h a l( ) , 3 5 2 Я з ы к и J a v a S c r ip t , N o d c .js , 4 3
V is it A ll, 5 5 Я з ы к п р о гр ам м и р о ван и я G o, 20
W a it G r o u , 7 7 H T M L -р а зм е тк а , 2 7
\ v a lk ( ) , 3 4 0 возврат н ескол ьки х значений, 23
Предме i ный укььа \ ель ❖ 373
Go на практике
Главный редактор Мпвч/т Д. А.
dmkpress@gmail.com
Научный редактор Киселев А. Н.
Перевод Рагимов Р. Н.
Корректор Синяева Г. И.
Верстка Чаннова А. А.
Д изайн обложки Мавчан А. Г.
Интернет-магазин: www.dmkpress.com
Книга - почтой: orders@alians-kniga.ru
Оптовая продажа: “ Альянс-книга” издательств о