Вы находитесь на странице: 1из 376

Go на практике

Matt Butcher, Matt Farina

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

Батчер М., Фарина М.


Б28 Go на практике / пер. с англ. Р. Н. Рагимова; науч. ред. А. Н.
Киселев. - М.: ДМК Пресс, 2017. - 374 с.: ил.
ISBN 978-5-97060-477-9
Go - превосходный системный язык. Созданный для удобной разработки совре­
менных приложений с параллельной обработкой, Go предоставляет встроенный на­
бор инструментов для быстрого создания облачных, системных и веб приложений.
Знакомые с такими языками, как Java или С#, быстро освоят Go - достаточно
лишь немного попрактиковаться, чтобы научиться писать профессиональный код.
Книга содержит решения десятков типовых задач в ключевых областях. Следуя
стилю сборника рецептов - проблема/решение/обсуждение, - это практическое
руководство опирается на основополагающие концепции языка Go и знакомит
с конкретными приемами использования Go в облаке, тестирования и отладки,
маршрутизации, а также создания веб-служб, сетевых и многих других приложений.
Издание адресовано опытным разработчикам, уже начавшим изучать язык Go
и желаюшим научиться эффективно использовать его в своей профессиональной
деятельности.

УДК 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)’.
Все права защищены. Л ю бая часть этой книги нс может быть воспроиз­
ведена в какой бы то ни было форме и какими бы то ни было средствами без
письменного разрешения владельцев авторских прав.
М атериал, изложенный в данной книге, многократно проверен. Но по­
скольку вероятность технических ошибок все равно существует, издательство
не может гарантировать абсолютную точность и правильность приводимых
сведений. В связи с этим издательство не несет ответственности за возможные
ошибки, связанные г. использованием книги.

ISBN 978-1-63343-007-5 (анг.) Copyright О 2016 by Manning


Publications Со.
ISBN 978-5-97060-477-9 (рус.) © Оформление, издание, перевод,
ДМ К Пресс, 2017
Содержание

П редисловие ................................................................................................ 9
Введение................................................................................ 11
Благодарности......................................................................12
О книге.................................................................................. 15
Об авторах ....................................................................................................17
Об изображении на облож ке.............................................18

Часть I. Основные понятия и принципы ......................... 19


Глава 1. Введение в язы к Go............................................. 20
1.1. Что представляет собой язык G o?.......................................................... 21
1.2. Примечательные особенности языка Go............................................... 23
1.2.1. Возврат нескольких значений......................................................... 23
1.2.2. Современная стандартная библиотека...........................................25
1.2.3. Параллельная обработка с помощью сопрограмм
и каналов........................................................................................................ 28
1.2.4. Go - больше, чем язы к...................................................................... 32
1.3. Место языка Go среди других языков программирования............... 38
1.3.1. С и Go....................................................................................................38
1.3.2. Java и Go............................................................................................... 40
1.3.3. Python, РНР и G o .............................................................................. 41
1.3.4. JavaScript, Node.js и G o ..................................................................... 43
1.4. Подготовка и запуск программы на языке G o ..................................... 45
1.4.1. Установка G o .......................................................................................45
1.4.2. Работа с Git, Mercurial и другими системами управления
версиями........................................................................................................ 46
1.4.3. Знакомство с рабочей областью......................................................46
1.4.4. Работа с переменными среды.......................................................... 47
1.5. Приложение Hello G o............................................................................... 47
1.6. Итоги............................................................................................................ 49
6 ❖ Содержание

Г л а в а 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

Ч а с т ь II. Н а д е ж н ы е п р и л о ж е н и я ............................................... 123


Г л а в а 4. О б р а б о т к а о ш и б о к и а в а р и й ....................................124
4.1. Обработка ошибок...................................................................................125
4.2. Система аварий........................................................................................ 134
4.2.1. Отличия аварий от ошибок............................................................135
4.2.2. Работа с авариями.............................................................................136
4.2.3. Восстановление после авари й ...................................................... 139
4.2.4. Аварии и сопрограммы................................................................... 145
4.3. Итоги.......................................................................................................... 154

Г л а в а 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

Глава 7 . О бслуж и ван и е и получение р е су р со в


и ф о р м ...........................................................................................................222
7.1. Обслуживание статического содержимого........................................ 223
7.2. Обработка форм....................................................................................... 238
7.2.1. Введение в формы........................................................................... 238
7.2.2. Работа с файлами и предоставление составных данных......... 241
7.2.3. Работа с необработанными составными данными.................... 249
7.3. Итоги..........................................................................................................253

Г л а в а 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

Ч а с т ь IV. Р а з м е щ е н и е п р и л о ж е н и й в о б л а к е ................. 281


Г л а в а 9 . И с п о л ь з о в а н и е о б л а к а ................................................282
9.1. Что такое облачные вычисления?.....................................................283
9.1.1. Виды облачных вычислений.......................................................283
8 Содержание

9.1.2. Контейнеры и натуральные облачные приложения.............. 286


9.2. Управление облачными службами....................................................... 288
9.2.1. Независимость от конкретного провайдера облачных
услуг............................................................................................................. 289
9.2.2. Обработка накапливающихся ошибок........................................ 293
9.3. Выполнение на облачных серверах...................................................... 295
9.3.1. Получение сведений о среде выполнения...................................295
9.3.2. Сборка для облака........................................................................... 299
9.3.3. Мониторинг среды выполнения................................................... 302
9.4. Итоги..........................................................................................................305

Глава 10. Взаимодействие облачных с л у ж б .................. 306


10.1. Микрослужбы и высокая доступность..............................................307
10.2. Взаимодействия между службами..................................................... 309
10.2.1. Ускорение R EST API.....................................................................309
10.2.2. Выход за рамки прикладного программного интерфейса
R EST............................................................................................................. 317
10.3. Итоги........................................................................................................327

Глава 11 . Рефлексия и генерация ко д а .............................. 328


11.1. Три области применения рефлексии................................................. 328
11.2. Структуры, теги и аннотации............................................................. 343
11.2.1. Аннотирование структур............................................................. 344
11.2.2. Использование тегов.....................................................................345
11.3. Генерация Go-кода с помощью Go-кода............................................ 354
11.4. Итоги........................................................................................................361

Предметный указатель 363


Предисловие

Когда я услышал, что Мотт Фарина и Мотт Батчер начали работу над
новой книгой о языке Go, это меня очень взволновало. Они оба много
лет были важнейшими действующими лицами в экосистеме Go, об­
ладают большим опытом работы и способны добавить в аромат со­
держания этой книги запахи специй прошлых учебных пособий. Эта
книга должна стать преемницей книги «Go in Action», развивающей
заложенные там основы и переводящей их в практическое русло.
Книга разбита на четыре простые в освоении части, каждая из ко­
торых имеет собственную направленность. Часть 1 освежает в памяти
основные идеи языка Go. Если вы спешите и уже обладаете навыка­
ми, позволяющими уверенно писать код на языке Go, можете смело
пропустить этот раздел, хотя я не рекомендую этого делать. Знако­
мясь с окончательным вариантом рукописи, я обнаружил в ней само­
родки такого размера, что, полагаю, главы этой части будут полезны
всем читателям.
Часть 2 погружает читателя в недра механики управления Go-
приложениями. Глава об ошибках является одним из лучших описа­
ний Go-ошибок из всех, прочитанных мной прежде, а глава, посвящен­
ная отладке pi тестированию, содержит массу полезной информации
об этом важном этапе разработки, помогающем поднять приложение
с уровня, требующего доказательства идеи, до уровня надежной про­
изводственной системы.
В третьей части вы узнаете о способах создания пользовательских
интерфейсов. Глава но шаблонам является отличным руководством
по самой сложной, как многие полагают, части экосистемы Go. Она
знакомит с практическими приемами многократного использования
шаблонов и создания выразительных веб-интерфейсов. Приведен­
ные примеры соответствуют уровню книги, поскольку трудно найти
примеры использования шаблонов, легко переносимые в реальные
приложения. Затем вы увидите, как создавать и использовать REST
API, и познакомитесь с хитростями управления версиями этого API.
Заключительная часть книги посвящена теме функциональной
совместимости, необходимой практически любому современному
приложению. Она позволяет глубоко погрузиться в облачную инф­
раструктуру и увидеть, как язык Go вписывается в модель облачных
10 Предисловие

вычислений. Заканчивается эта часть широким обзором микрослужб


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

—Брайан Кетелсен (Brian Ketelsen),


один их авторов книги «Go in action»,
один из основателей «Gopher academy»
Введение

При первом знакомстве с языком Go мы сразу оценили его большой


потенциал. У нас появилось желание писать на нем приложения. Но
это был новый язык, а во многих компаниях опасаются использовать
новые языки программирования.
Это особенно касается тех компаний, где потенциал языка Go на­
шел бы широкое применение. Новым языкам приходится добиваться
доверия, признания и принятия. Сотни тысяч разработчиков заняты
в бизнесе, лидеры которого долго колеблются, перед тем как попро­
бовать новый язык, а разработчикам, чтобы попять его выгоды и при­
менить в разработке приложений, необходимо достаточно хорошо
изучить язык.
Продвижению нового языка способствуют проекты с открытым
исходным кодом, конференции, курсы и книги. Цель этой книги -
помочь в изучении языка Go всем желающими, внести наш вклад
в развитие Go-сообщества и разъяснить перспективы применения
языка Go руководству компаний, занятых в том числе разработкой
программного обеспечения.
Начиная работу над книгой, мы представляли ее целиком посвя­
щенной применению Go в разработке облачных приложений. Мы
уже несколько лет работаем в сфере облачных вычислений, a Go - это
язык, специально созданный для нее. Начав сотрудничать с издатель­
ством Manning, мы сочли возможным выйти за пределы облачных
технологий и дополнительно охватить некоторые полезные и прак­
тичные шаблоны программирования. В результате центр внимания
книги сместился из сферы облачных вычислений в сферу практиче­
ского применения Go. Тем не менее корнями она по-прежнему уходит
в облачные технологии.
Книга «Go на практике» —это наша попытка помочь разработчи­
кам познакомиться с языком для продуктивного его использования,
а также помочь развитию сообщества и способствовать улучшению
разрабатываемого программного обеспечения.
Благодарности

На написание этой книги мы потратили около двух лет, но ее за­


вершение стало бы невозможным без поддержки наших семей. Они
поддерживали нас с раннего утра до позднего вечера и в выходные
дни, когда мы целиком уходили в работу. Они были рядом, когда
мы не писали, но были поглощены разрешением связанных с книгой
проблем.
Хорошие программы не пишутся в вакууме. Мы также призна­
тельны членам Go-сообщества, столь щедро отдавшим свое время
созданию языка, его библиотек и процветанию его экосистемы. Ув­
лекательно быть частью такого разнообразного и динамичного со­
общества разработчиков. Мы, в частности, благодарим, Роба Пайка
(Rob Pike), Брайана Кетелсена (Brian Ketelsen) и Дэйва Чейни (Dave
Cheney), которые помогли нам на начальном этапе изучения Go. Они
отлично справляются с ролью эмиссаров языка. Особая благодар­
ность Брайану за предисловие к книге и одобрение нашей работы.
Мы ценим помощь множества людей, которые пожертвовали сво­
им временем и приложили усилия к созданию этой книги. Это был
трудный путь. Мы благодарны многим внимательным читателям,
в том числе и нашим рецензентам, обнаружившим многочисленные
ошибки, что позволило их своевременно исправить.
Мы хотели бы поблагодарить всех сотрудников издательства Man­
ning, особенно нашего редактора Сусанну Клайн (Susanna Kline),
научных редакторов Айвана Киркпатрика (Ivan Kirkpatrick), Кима
Шириера (Kim Shrier), Гленна Бернсайда (Glenn Burnside) и .Алена
Коуниот (Alain Cournot), технического корректора Джеймса Фрасче
(James Frasche), а также всех, кто трудился над нашей книгой. Боль­
шое спасибо рецензентам, нашедшим время па чтение нашей рукопи­
си на различных стадиях ее готовности и написание отзывов, ока­
завших нам неоценимую помощь: Энтони Крампу (Anthony Cramp),
Остину Риендо (Austin Riendeau), Брэндону Титусу (Brandon Titus),
Дугу Спарлингу (Doug Sparling), Фердинандо Сантакосе (Ferdinando
Santacroce), Гари А. Стаффорду (Gary A. Stallord), Джиму Амрхайну
(Jim Amrhein), Кевину Мартину (Kevin Martin), Натану Дэвису (Na­
than Davies), Квинтину Смиту (Quintin Smith ), Сэму Зайделу (Sam
Zaydel) и Уэсу Шэддиксу (Wes Shaddix).
Благодарности 13

Наконец, мы благодарны сообществу Glide, сформировавшемуся


за время работы над созданием диспетчера пакетов верхнего уровня
для Go. Благодарим вас за вашу поддержку.

Я начал писать эту книгу, работая в компании Revolv, про­


должил после приобретения ее компанией Google/Nest и за­
кончил в компании Deis. Спасибо им всем за поддержку в созда­
нии этой книги. Благодарю Брайана Хардока (Brian Hardock),
Кристиана Кавалли (Cristian Cavalli), Ланна Мартина (Lann
Martin) и Криса Чинга (Chris Ching), всех, с кем я советовал­
ся. Мэтт Боерсма (M att Boersma) написал полезные отзывы
о нескольких главах. Кент Rancourt (Kent Rancourt) и Аарон
Шлезингер (Aaron Schlesinger) подали идею для нескольких
примеров в книге. Мэтт Фишер (M att Fisher), Сиварам Мози-
ки (Sivaram Mothiki), Кирзан Мала (Keerthan Mala), Хельги
Порбьёрнссон (Heigi borbjornsson) (да, Хельги, я скопировал
и вставил это), Гейб Монрой (Gabe Monroy), Крис Армстронг
(Chris Armstrong), Сэм Бойер (Sam Boyer), Джефф Блейел (.Jeff
Bleiei), Джошуа Андерсон (Joshua Anderson), Римас Мосеви-
киус (Rimas Moccvicius), Джек Фрэнсис (Jack Francis) и Джош
Лэйн (Josh Lane) - все вы (вольно или невольно) оказали влия­
ние на определенные фрагменты этой книги. Нельзя недооце­
нивать влияние Мишеля Ноорали (Michelle Noorali) и М ам а
Риза (Adam Reese). Я многому научился, наблюдая за рабо­
той нескольких Ruby-разработчиков, знатоков Go. И спасибо
Энджи (Angie), Аннабсль (Annabcllc), Клэр (Claire) и Кэтрин
(Katherine) за их постоянную поддержку и понимание.
- Мэтт Батчер (Matt Butcher)

Я хотел бы поблагодарить мою красивую и удивительную


жену Кристину, а также наших прекрасных дочерей, Изабеллу
и Обри, за их любовь и поддержку.
Я писал эту книгу, работая в компании Hewlett Packard Enter­
prise, ранее называющейся Hewlett-Packard. Работая в компании
Hewlett Packard Enterprise, я многому научился, общаясь с теми,
кто был гораздо умнее меня. В частности, я должен поблагода­
рить Раджива Пандей (Rajeev Pandey), Брайана Акера (Brian
Aker), Стива Маклеллана (Steve McLellan), Эрин Handgen (Erin
Handgen), Эрика Густафсона (Eric Gustafson), Майка Хагедорна
(Mike Hagedorn), Сьюзан Балле (Susan Balle), Дэвида Гравеса
14 Благодарности

(David Graves) и многих других. Они учили меня писать при­


ложения и управлять ими, и это, безусловно, отразилось на
данной книге.
Многие другие также оказали влияние па определенные
фрагменты этой книги, иногда даже сами не осознавая этого.
Благодарю Тима Плетчера (Tim Pletclier), Джейсона Бубере-
ла (Jason Buberel), Сэм Бойера (Sam Boyer), Ларри Гарфилда
(Larry Garfield) и всех тех, кого я мог забыть.
Наконец, я хочу поблагодарить Мэтта Батчера. Я никогда не
задумывался над тем, чтобы стать автором книги, пока ты не
предложил мне это. Спасибо!
- Мэтт Фарина (M att Farina)
О книге

Книга «G o на практике» описывает практические приемы програм­


мирования на языке Go. Разработчики, знакомые с основами Go,
найдут в ней шаблоны и методики создания Go-приложений. Каждая
глава затрагивает определенную тему (как, например, глава 10 «В за­
имодействие облачных служб») и исследует различные технологии,
относящиеся к ней.

К а к организована книга
Одиннадцать глав разделены на четыре части.
Часть I «Основные понятия и принципы» закладывает фундамент
для будущих приложений. Глава 1 описывает основы языка Go и бу­
дет полезна всем, кто еще нс знаком с ним или хотел бы узнать о нем
больше. В главе 2 рассказывается о создании консольных приложе­
ний и серверов, а в главе 3 - об организации параллельных вычисле­
ний в Go.
Часть II «Падежные приложения» включает главы 4 и 5, охваты­
вающие темы ошибок, аварий, отладки и тестирования. Цель этого
раздела рассказать о создании надежных приложений, способных
автоматически справляться с возникающими проблемами.
Часть III «Интерфейсы приложений» содержит три главы и охва­
тывает диапазон тем, от генерации разметки H TM L и обслуживания
ресурсов до реализации различных API и работы с ними. Многие
Go-приложения поддерживают взаимодействие через веб-интерфейс
и R EST API. Эти главы охватывают приемы, помогающие в их кон­
струировании.
Часть IV «Облачные приложения» содержит остальные главы, по­
священные облачным вычислениям и генерации кода. Язык Go разра­
батывался с учетом нужд облачных технологий. В этой части демон­
стрируются приемы, позволяющие работать с облачными службами
и приложениями и даже с микрослужбами в них. Кроме того, она
охватывает приемы генерации кода и метапрограммирование.
В книге описывается 70 приемов, и в каждом случае сначала ста­
вится задача, затем дается решение и в заключение обсуждаются при­
чины выбора тех или иных подходов.
16 О книге

Соглашения и загрузка примеров кола


Весь исходный код в книге оформлен моноширинным шрифтом, как
этот текст, чтобы выделить его в окружающем тексте. Больш инство
листингов сопровождаются указателями на ключевые понятия или
пронумерованными маркерами для ссылки на них в тексте с поясне­
ниями к коду.
Исходный код примеров можно загрузить на сайте издательства,
по адресу: ww w.m anning.com /books/go-in-practice, или из репозито­
рия GitHub: github.com /M asterm inds/go-in-practice.

Авторский интернет-форум
Приобретая книгу «G o на практике», вы получаете свободный доступ
к закрытому веб-форуму издательства Manning Publications, где м ож­
но оставить отзыв о книге, задать технические вопросы и получить
помощь авторов или других пользователей. Чтобы получить доступ
к форуму и зарегистрироваться на нем, перейдите на страницу www.
m anning.com /books/go-in-practice. Здесь вы найдете информацию
о том, как пользоваться форумом после регистрации, какого рода по­
мощь можно получить и правила поведения на форуме.
Издательство Manning, идя навстречу пожеланиям своих читате­
лей, предоставляет площадку для конструктивного диалога между
читателями, а также между читателями и авторами. Это не обязывает
авторов к участию в нем —любое их участие является добровольным
(и никак не оплачивается). Задавайте авторам сложные вопросы, что­
бы заинтересовать их!
Авторский интернет-форум и архивы предыдущих дискуссий будут
доступны на веб-сайте издателя, пока книга продолжает печататься.
Об авторах

Мэтт Батчер - архитектор программного обеспе­


чения в компании Deis, где занимается проекта­
ми с открытым исходным кодом. Написал не­
сколько книг и множество статей. Имеет
докторскую степень и преподает на факультете
информационных технологий в университете
Лойола (Чикаго, США). Мэтт увлечен создани­
ем сильных команд и разработкой элегантных
решений сложных проблем.

Мэтт Фарина работает в должности ведущего


инженера группы передовых технологий в ком­
пании Hewlett Packard Enterprise. Технический
писатель, лектор и регулярно вносит свой вклад
в проекты с открытым исходным кодом. Занима­
ется разработкой программного обеспечения
уже более четверти века. Любит решать пробле­
мы, применяя не только новейшие, но и самые
обычные технологии, о которых часто забывают.
Об изображении
на обложке

Рисунок на обложке книги «Go на практике» называется «Типичная


одежда русской крестьянки, 1768 год». Иллюстрация взята из книги
«Collection of the Dresses of Different Nations, Ancient and Modern»
(Коллекция костюмов разных народов, античных и современных)
Томаса Джеффериса (Thomas Jeffervs), опубликованной в Лондоне
между 1757 и 1772 годом. В подписи к странице было указано, что
она представляет выполненную вручную калл играф ичес кую цвет­
ную гравюру, обработанную аравийской камедью.
Томас Джефферис (1719-1771) носил звание «Географ короля
Георга III». Английский картограф, был ведущим поставщиком карт
того времени. Он выгравировал и напечатал множество карт для нужд
правительства, других официальных органов и широкий спектр ком­
мерческих карт и атласов, в частности Северной Америки. Будучи
картографом, интересовался местной одеждой народов, населяющих
разные земли, и собрал блестящую коллекцию различных платьев
в четырех томах.
Очарование далеких земель и далышх путешествий для удоволь­
ствия было относительно новым явлением в конце XVTTT века, и коллек­
ции, такие как эта, были весьма популярны, знакомя с внешним видом
жителей других стран. Разнообразие рисунков, собранных Джеффери­
сом, свидетельствует о проявлении народами мира около 200 лет яркой
индивидуальности и уникальности. С тех пор стиль одежды сильно из­
менился, и исчезло разнообразие, характеризующее различные обла­
сти и страны. Теперь трудно отличить по одежде даже жителей разных
континентов. Если взглянуть на это с оптимистической точки зрения,
мы пожертвовали культурным и внешним разнообразием в угоду раз­
нообразию личной жизни, или в угоду более разнообразной и интерес­
ной интеллектуальной и технической деятельности.
В наше время, когда трудно отличить одну техническую книгу от
другой, издательство Manning проявило инициативу и деловую смет­
ку, украшая обложки книг изображениями, основанными на богатом
разнообразии жизненного уклада народов двухвековой давности,
придав новую жизнь рисункам Джеффериса.
Насть

ОСНОВНЫЕ ПОНЯТИЯ
И ПРИНЦИПЫ

та часть книги содержит базовые сведения о языке Go и описание


Э фундаментальных принципов разработки приложений на нем.
Глава 1 начинается с обзора язы ка Go для тех, кто еще с ним не зн а­
ком.
Главы 2 и 3 охватываю т основные компоненты приложений. Гла­
ва 2 описывает основы разработки приложений, включая консольные
и серверные, и приемы их настройки. Глава 3 рассматривает исполь­
зование сопрограмм Go. Сопрограммы являются одним из наиболее
мощных и полезных элементов языка Go. Они широко используются
в G o-приложениях, и вы часто будете сталкиваться с ними на про­
тяжении всей книги.
Глава

Введение в язык Go

В этой главе рассматриваются следующие темы:


О введение в язык Go;
О место языка Go в ландшафте языков программирования;
О подготовка к работе с языком Go.

С течением времени меняется подход к разработке и з а п у с к у про­


граммного обеспечения. Инновации трансформируют понятие о вы­
числительной среде, где выполняются программы. Чтобы полностью
использовать преимущества нововведений, необходимы языки и ин­
струменты, изначально их поддерживающие.
Во времена, когда создавалось большинство популярных ныне
языков программирования и поддерживающих их инструментов,
существовали только одноядерные процессоры, соответственно, они
проектировались с прицелом на работу в однопроцессорной среде.
В настоящее время настольные компьютеры, серверы и даже телефо­
ны оснащены многоядерными процессорами. На любом из них можно
выполнять программы с параллельными операциями.
Меняются инструменты разработки приложений. Увеличение
сложности программного обеспечения требует окружений, способ­
ных быстро собирать код и эффективно его выполнять. Тестирование
больших и сложных приложений должно выполняться быстро, чтобы
не тормозить процесс разработки. Многие приложения используют
библиотеки. Благодаря решению проблемы нехватки дискового про­
странства появилась возможность поддерживать несколько версий
библиотек.
Меняется подход к предоставлению инфраструктуры и программ-
н о т обеспечения. Использование групп серверов, размещаемых
в одном месте, и простое выделение виртуальных частных серверов
стали нормой. Прежде масштабирование служб, как правило, озна-
Что представляет собой язык Go? ❖ 21

чало необходимость инвестиций в собственное оборудование, вклю ­


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

1.1. Что представляет собой язык Со?


Я зы к Go, который часто н азы ваю т golang, чтобы упростить поиск све­
дений о нем в Интернете, — это статически типизированный и ком­
пилируемый язы к программирования с открытым исходным кодом,
разработку которого начинала компания Google. Роберт Грисемер
(R obert Griesem er), Роб П айк (R o b Pike) и Кен Томпсон (Ken Thom p­
son) сделали попытку создать язы к для современных программных
систем, способных реш ать проблемы, с которыми они столкнулись
при м асш табировании больш их систем.
Вместо попытки достичь теоретического соверш енства создатели
язы ка Go отталкивались от ситуаций, часто возникаю щ их на практи­
ке. И х вдохновлял опыт ведущ их языков, созданных прежде, таких как
С, Pascal, Smalltalk, Newsqueak, С # , JavaScrip t, Python, Ja v a и других.
Go - не обычный статически типизированный и компилируемый
язык. Его статическая типизация имеет черты, делающие ее похожей
на динамическую, а скомпилированные двоичные файлы вклю чаю т
среду выполнения со встроенным сборщ иком мусора. Н а структуру
язы ка наложили отпечаток проекты, для которых он должен был и с­
пользоваться в Google: большие проекты, поддерживаю щие м асш та­
бирование и разрабаты ваем ы е многочисленными группами разработ­
чиков.
По сути, Go —это язы к программирования, определяемый специ­
фикациями, которые можно реализовать в лю бом компиляторе. И х
реализация по умолчанию распространяется в виде инструмента до.
Н о G o - это не просто язы к программирования. На рис. 1.1 изображ е­
на его многослойная структура.
Для разработки приложений нужен не только язы к программиро­
вания, а также средства тестирования, документирования и ф орм ати­
рования исходного кода. И нструмент со, обычно используемый для
22 Введение в язык Go

компиляции приложений, поддерживает также все вышеперечислен­


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

Что представляет собой язы к Go?


Язык программирования: Go - современный
язык для использования в современных
аппаратных архитектурах
Набор инструментов разработки:
для поддержки разработки Go-приложений,
включает встроенные средства тестирования,
документирования, форматирования
и многие другие
Экосистема: Go включает встроенное
средство управления пакетами, поддер­
живающее системы управления исходным
кодом, такие как Git. Экосистема пакетов
и библиотек предназначена для поддержки
Go-приложений

Рис. 1.1 ❖ Слои языка Go

Одной из определяющих особенностей С о является его простота.


Когда Грисемер, Пайк и Томпсон начинали проектирование языка,
новые функциональные возможности не включались в язык, пока все
трос нс приходили к согласию об их необходимости. Такой стиль при­
нятия решений, а также их многолетний опыт привел к созданию про­
стого и мощного языка. Он прост в изучении, но достаточно мощный
для широкого спектра программного обеспечения.
Ф илософ ию язы ка можно проиллюстрировать примером синтак­
сиса объявления переменной:
var i int = 2
Здесь создается целочисленная переменная, и ей присваивается
значение 2. Поскольку имеется присваивание начального значения,
определение можно сократить до:
var i = 2
При наличии начального значения компилятор автоматически
определяет по нему тип. В данном случае компилятор обнаружит зн а­
чение 2 и поймет, что переменная должна иметь целочисленный тип.
П р и м е ча i ельные особенное i и языка Go 23

Но язык Go не останавливается на этом. А нужно ли само ключе­


вое слово var? Нет, потому что Go поддерживает краткое объявление
переменных'.
i := 2

Это краткий эквивалент первой инструкции определения пере­


менной. Он более чем вдвое короче, легко читается, и все это за счет
автоматического определения компилятором недостающих частей.
Простота Go означает, что он поддерживает не все возможности,
имеющиеся в других языках. Например, в языке Go отсутствуют
тернарный оператор (обычно это ? :) и обобщенные типы. Не со­
держит он и некоторых других функциональных возможностей,
присутствующих в современных языках, что служит поводом для
его критики, но это не должно служить поводом для отказа от ис­
пользования языка Go. В мире программирования одна и та же зада­
ча часто может быть решена множеством способов. Даже если в Go
отсутствуют какие-то возможности, имеющиеся в других языках,
вместо них он предоставляет иные пути решения проблем, ничуть
не хуже.
Несмотря на простоту ядра языка Go, встроенная система управле­
ния пакетами позволяет добавлять в него дополнительные возможно­
сти. Многие из недостающих элементов можно подключить как сто­
ронние пакеты и включить в приложение.
Минимальный размер и сложность дают свои преимущества. Язык
можно быстро освоить, и он хорошо запоминается. Это важное пре­
имущество, когда требуется быстро исследовать чужой код.

1.2. Примечательные особенности


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

1.2.1. Возврат нескольких значений


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

функции только одного значения. Если требуется вернуть несколько


значений, они встраиваются в кортеж, хэш или любое другое значе­
ние составного типа, возвращаемое функцией. Go - один из немногих
языков, естественным образом поддерживающих возврат нескольких
значений. Эта возможность используется в нем повсеместно, в чем
легко убедиться, заглянув в исходный код библиотек и приложе­
ний, написанных на языке Go. Для примера рассмотрим следующую
функцию, возвращающую две строки с именами.
Л и с т и н г 1.1 ❖ Возврат нескольких значений: re tu rn s .до
package train

inport (
"fmt"

func Nair.es() (strin g, string) <r О Определена как возвращающая две строки
return "Foo", "Bar" <- О Возвращаются две строки

func main() {
nl, n2 := Names () © Значения присваиваются двум переменным
fmt. Frintln (til, п2) и выводятся

n3, _ := Names () ч- О Первое возвращаемое значение сохраняется,


fmt. Frintln (пЗ) второе - отбрасывается

СОВЕТ Импортируемые пакеты, что используются в этой главе,


такие как fmt, bufio, net и другие, входят в состав стандартной биб­
лиотеки. Более подробную информацию о них, включая описание
программных интерфейсов и особенностей работы, можно найти
на странице: h ttp s ://g o la n g .o rg /p k g .

Как показано в этом примере, типы возвращаемых значений объяв­


ляются в определении функции, после списка параметров О. В дан­
ном случае функция возвращает два строковых значения. Оператор
return возвращает две строки ©. в соответствии с определением. Вы­
зывая функцию Names, необходимо предоставить переменные для всех
возвращаемых значений ©. Но, если требуется сохранить только одно
из возвращаемых значений, для отбрасываемою значения укажите
специальную переменную _ ©. (Можете особенно нс беспокоиться
о деталях реализации в этом примере. Мы еще вернемся к понятиям,
библиотекам и инструментам, использованным здесь, в следующих
главах.)
Примеча I ельные особенное i и чаыка Go 25

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


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

Листинг 1.2 ❖ Именованные возвращаемые значения: re:crns2.go


package ir.ain
inport (
"fmt"
Именованные
func Nair.es () (first string, second string) { «- О возвращаемые значения
first = "Foo" © Присваивание значений именованным
second = "Ваг” возвращаемым переменным
return ч- © Оператор return вызывается без значений

func main() {
nl, n2 := Names () < - О Значения присваиваются двум переменным
fmt.Printlnlnl, п2)

Ф ункция Names возвращ ает именованные переменные О, содержа­


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

1.2.2. Современная стандартная библиотека


Современные приложения решают определенные типовые задачи, та­
кие как сетевые операции или шифрование. И чтобы не обременять
разработчиков поиском библиотек для решения этих задач, стандарт­
ная библиотека Go изначально включает такие полезные функции.
Остановимся на некоторых элементах стандартной библиотеки, что­
бы получить общее представление о ее содержимом.
ПРИМЕЧАНИЕ Полное описание стандартной библиотеки с при­
мерами можно найти на странице: http://golang.org/pkg/.

Сетевые операции и HTTP


Под сетевыми приложениями подразумеваются и клиентские при­
ложения, способные подключаться к другим сетевым устройствам,
26 Введение в язы к Go

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


ниям (листинг 1.3). Стандартная библиотека Go легко со всем этим
справляется, будь то работа по протоколу HTTP, TC P (Transmission
Control Protocol), UDP (User Datagram Protocol) или с применением
других типовых возможностей.
Л и с т и н г 1.3 ♦> Чтение состояния по протоколу TCP: read s ta tu s .дс
package та in

: 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)

Непосредственное подключение к порту обеспечивает пакет net,


в котором язык Go предоставляет типовые настройки для различных
типов соединений. Функция Dial О при подключении использует
указанный тип и конечную точку. В данном случае создается ТСР-
соединение с портом 80 но адресу golang.org. После подключения по­
сылается запрос GET © и выводится первая строка ответа ©.
Прием входящих запросов на соединение реализуется так же прос­
то. Только вместо функции Dial используется функция Listen из па­
кета net, которая переводит приложение в режим приема входящих
соединений.
Протокол HTTP, службы R EST (Representational State Transfer)
и веб-серверы широко используются для взаимодействий в сети. Для
их поддержки в язык Go был включен пакет http, содержащий реа­
лизации клиента и сервера (пример его использования приводится
в листинге 1.4). Клиент достаточно прост в использовании, посколь­
ку отвечает обычным повседневным потребностям и допускает рас­
ширение, охватывающее сложные случаи.
Примеча i ельные особенное i и языка Go ❖ 27

Л и с ти н г 1.4 ♦> HTTP-запрос GET: h ttp _ge t.go


package main

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 () <- Закрытие соединения

Этот пример демонстрирует вывод тела простого НТТР-занроса


GET. H TTP-клиент может намного больше, например работать с прок­
си, обрабатывать шифрование TLS, устанавливать заголовки, обраба­
тывать cookie, создавать клиентские объекты и даже подменять весь
транспортный уровень.
Создание H TTP-сервера на языке Go - весьма распространенная
задача. Стандартная библиотека Go обладает мощными возможно­
стями, поддерживающими масштабирование, простыми в освоении
и достаточно гибкими для применения в сложных приложениях. Соз­
данию H TTP-сервера и работе с ним будет посвящена глава 3.

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 в экземпляры объ­
ектов, а интерфейсы обеспечивают переключение между кодировка­
ми и добавление новых способов работы с кодировками посредством
подключения внешних пакетов.

1.2.3. Параллельная обработка с помощью


сопрограмм и каналов
Многоядерные процессоры стали обычным явлением. Они приме­
няются во многих устройствах, начиная с серверов и заканчивая мо­
бильными телефонами. Однако большинство языков программиро­
вания разрабатываюсь под одноядерные процессоры, поскольку на
тот момент только они и существовали.
Кроме того, среда выполнения в некоторых языках имеет глобать-
ную блокировку, что затрудняет параллельное выполнение процедур.
Примеча I ельные особенности языка Go 29

Язык Go изначально ориентировался на параллельную и конкурент­


ную обработку.
В языке Go имеются так называемые сопрограммы, или go-подпрог-
раммы, - процедуры, способные выполняться параллельно с основ­
ной программой и другими сопрограммами. Иногда называемые лег­
ковесными потоками, сопрограммы управляются средой выполнения
Go, которая помещает их в соответствующие потоки операционной
системы и утилизирует их с помощью сборщика мусора, когда они
становятся ненужными. При наличии в системе процессора с несколь­
кими ядрами go-подпрограммы способны выполняться параллельно,
поскольку различные потоки могут выполняться одновременно на
разных ядрах. Для разработчика создание сопрограмм выглядит не
сложнее написания обычных функций. Рисунок 1.2 иллюстрирует
работу go-подпрограмм.

Сопрограммы - функции,
выполняющиеся
до-подпрограмма ^ конкуренте, - рабшакл
в потоках, управляемых
до-подпрограмма планировщиком Go
Планировщик Go способен
^ до-подпрограмма ^ перемещать сопрограммы
между потоками операци­
... онной системы. Если поток
заблокирован, скажем
до-подпрограмма ^ операцией ввода-вывода,
другие сопрограммы могут
Поток перемещаться в другие
потоки
Обрабатывающее ядро

Современные вычислительные системы имеют многоядерные


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

Рис. 1.2 ❖ Go-подпрограммы выполняются в потоках,


распространяемых на все доступные ядра

Для иллюстрации рассмотрим сопрограмму, ведущую счет от 0 до 4


одновременно с тем, как основная программа печатает Не11э World, как
показано в листинге 1.5.
Листинг 1.5 ❖ Результат конкурентного вывода
о

H e llo W o r ld 2
3
4
30 Введение в язык Go

Такой вывод получается в результате одновременного выполнения


двух функций. С оответствую щ ий код, следующий ниже, напоминает
обычную процедурную программу, с небольш им исключением.

Л истинг 1.6 ❖ Конкурентный вывод


package main
import (
"fmt"
"time"

func count 0 { О Функция, выполняемая


for i := 0; i < b; i++ { как до-подпрограмма
fmt.Println(i)
time.Sleep(time.Millisecond * 1)
}

func main() {
qo count () <- 0 Вызов до-подпрограммы
time.Sleep(time.Millisecond * 2)
fmt.Frintln("Hello World")
time.Sleep(time.Mi Hi second * 5)

Ф ун кция count О - это самая обычная функция, которая выводит


числа от 0 до 4. Чтобы запустить ее параллельно с основной програм­
мой, используется ключевое слово до ©. Благодаря этому функция
main продолжает работать, как обычно, после вы зова функции count.
В результате функции count и main вы полняться одновременно.
Передача данных между еопрограммами может осущ ествляться
через каналы. По умолчанию они блокирую т выполнение, позволяя
сопрограммам синхронизироваться д р у ге другом. Рисунок 1.3 иллю ­
стрирует это на простом примере.
В этом примере переменная передается от одной сопрограммы
к другой через канал. Э та операция работает, даже когда сопрограммы
выполняю тся параллельно, на различных ядрах процессора. В при­
мере показана однонаправленная передача информации, но вообщ е
каналы могут быть не только однонаправленными, но и двунаправ­
ленными.
В следующ ем листинге приводится пример использования канала.
Примечательные особенности языка С о ❖ 31

Этап 1. Сопрограмма содержит экземпляр типа

Этап 2. Сопрограмма передает экземпляр в канал

Этап 3. Канал передает экземпляр другой до-подпрограмме

Рис. 1.3 •> Передача переменных между до-подпрограммами

Л и с ти н г 1.7 ♦> Использование каналов: channel.до


package train import (
"fmt"
"tiir.e"

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 <- © Запись целого значения в канал

time.Sleep (time.Millisecond * 1) *- @ Функция main приостанавливается


fmt.Friiitln("End of main") перед завершением

В начале функции .наin создается кала./г с для передачи целого чис­


ла © между сопрограммами. Функция orintCcunt, вызываемая как
сопрограмма, передает значение в канал О. Согласно определению
32 Введение в язык Go

парам етра функции printC ount, канал идентифицируется как канал


для передачи целых чисел О . В цикле f o r ф ункция p rintC ount ож и да­
ет появления в канале с целого числа © и присваивает его перемен­
ной пиш. Тем временем функция main вы полняет обход списка целых
чисел и поочередно передает их r канал с ©. П осле передачи в канал
очередного целого числа из n a in 0 оно присваивается переменной nun
в функции p rin tC o u n t ©. Ф у н к ц и я prin tC o u n t продолж ит выполнение
цикла, пока в следую щ ей итерации вновь ис достигнет оператора ч те­
ния из капала © , и снова приостановится до появления в канале сл е­
дую щ его целого значения. П осле очередной итерации в функции main
и записи нового целого значения вы полнение продолж ится без задер ­
жек. По заверш ении функции main будет закончено вы полнение всей
программы, поэтом у необходимо вы полнить паузу в одну секунду ©,
чтобы позволи ть функции prin tC o u n t завер ш и ть вы полнение раньш е,
чем это сделает функция main. Если .запустить этот код, он вы ведет
следующее.

Листинг 1.8 ❖ В ы вод с использованием канала


8 6 7 5 3 0 9 - 1 End of main

С овм естное использование каналов и сопрограмм обеспечивает


возмож ности, напоминаю щ ие легковесны е потоки вы полнения или
внутренние микрослуж бы , обмениваю щ иеся данными через специа­
лизированны й прикладной программны й интерфейс. Они могут объ­
единяться в цепочки или ком бинироваться различным и способами.
С о-подпрограм м ы (сопрограм м ы ) и каналы являю тся двумя сам ы ­
ми мощ ными особенностями язы к а Go, которые не р аз будут упом и­
наться на протяж ении всей книги. Вы увидите, как и сп ользовать их
для реализации серверов, передачи сообщ ений и задерж ки вы полне­
ния задач. Такж е будут описаны ш аблоны проектирования, основан­
ные на сопрограм м ах и каналах.

1.2.4. Go - больше, чем язык


Р азр аботка современных м асш табируем ы х и простых в обслуж ива­
нии приложений требует м нож ества элементов. К ом пиляция - лиш ь
один из этапов в этом процессе. Так было задум ано изначально. G o -
это больш е, чем язы к и компилятор. Утилита до это целый комплекс
инструментов для управления пакетами, тестирования, докум енти­
ровани я и многого другою , помимо компиляции кода н а язы ке Go
в исполняемые файлы. Рассм отрим несколько компонентов в этом
наборе.
Примеча I ельные осо бе нноеги языка G o ❖ 33

Управление пакетами
Диспетчеры пакетов существуют для многих современных языков
программирования, но у скольких из них функция управления паке­
тами является встроенной? В языке Go она встроенная, и тому есть
две веские причины. Самая очевидная - продуктивность программис­
та. Вторая причина - ускорение компиляции. Управление пакетами
разрабатывалось с учетом нужд компилятора. Это один из аспектов,
определяющих быстроту компиляции.
Идею пакетов в Go проще всего изучать па примере стандартной
библиотеки (ее демонстрирует следующий листинг), которая органи­
зована на основе системы управления пакетами.

Листинг 1.9 ❖ Простой импорт пакета


packaqe ir.ain

import "fmt" « - Импорт пакета fmt

func main() {
fmt. F r i n t l n ("Hello World!") <- Использование функции пакета fmt

Пакеты импортируются по именам. В данном случае fmt - это па­


кет с функциями форматирования. Все имеющееся в пакете доступно
при использовании имени пакета в виде префикса. Как, например,
вызов fmt.Printlr. в предыдущем примере.
Импортируемые пакеты можно группировать, но в этом случае они
должны указываться в алфавитном порядке. После импортирования
пакетов, как показано ниже, на пакет n e t/h ttp можно ссылаться, ис­
пользуя префикс http.
import (
"fmt"
" n e t/h t tp "
)
Механизм импортирования работает также с пакетами, не входя­
щими в стандартную библиотеку Go, и ссылки на такие пакеты ис­
пользуются точно так же:
import (
"qolan q .o rg /x /n et/h tm l" к - Ссылка на внешний пакет го адресу URL
"fmt"
" n e t/h ttp "
34 Введение в язык Go

Имена пакетов являю тся уникальными строками и могут быть чем


угодно. Здесь для ссылки на внешний пакет использован U R L -адрес.
Это позволяет языку Go опознать уникальный ресурс и получить его.
S go g e t ./...
Команда go g et принимает путь к отдельному пакету', например golang.
org/x/net./htirl, для загрузки отдельного пакета, или . / __ R послед­
нем случае Go выполнит обход базы кода и загрузит все внешние па­
кеты. В данном случае (см. пример вы ш е) Go обнаружит инструкцию
iir.por", выделит из нее внешнюю ссылку, загрузит пакет и сделает его
доступным в текущей рабочей области.
Я зы к Go может загруж ать пакеты из систем управления версиями.
Он поддерживает Git, Mercurial, SV N и Bazaar, если они установлены
в локальном окружении. В этом случае Go загруж ает базу кода из Git
и проверяет последнюю версию в ветви по умолчанию.
Система управления пакетами поддерживает далеко не все, чего
можно было бы пожелать. Она реализует лишь базовые возмож но­
сти, которые можно использовать непосредственно или как основу
для системы с более широкими возможностями.

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

Листинг 1.10 ❖ Пример Hello World: hello.до


package ir.ain

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

Начнем с варианта приложения «Hello World», содержащего функ­


цию getName, которая и будет тестироваться. В соответствии с согла­
шением в языке Go имена файлов с тестами заканчиваются на te st,
gc. Данный суффикс сообщает среде Go, что этот файл предназначен
для тестирования и должен игнорироваться при сборке приложения.

Листинг 1.11 ❖ Тест для примера Hello World: h e l lo t e s t . до


package train

: nport "testin g"

tunc TestName (t * t e s t i n g . T) { <- О Инструмент запуска тестов вызывает функции,


name := getName 0 начинающиеся с Test
i f name != "World!" { © Отчет об
t.Error("R espone from getName is unexpected value") ошибке, если тест
} не пройден

Если запустить команду go test, она выполнит функцию с име­


нем, начинающимся с префикса Test О, - в данном случае функцию
TestName - и передаст ей вспомогательную структуру t. Структура со­
держит несколько полезных функций, например для вывода сообще­
ний об ошибках. За дополнительной информацией обращайтесь к до­
кументации описанием пакета testing. Если значение переменной
name окажется неправильным, тест выведет сообщение об ошибке ©.
В следующем листинге показан вывод команды go test, сообщаю­
щий имя пакета и результаты его тестирования. Для проверки теку­
щего пакета и всех пакетов во вложенных подкаталогах можно ис­
пользовать команду gc . / __

Листинг 1.12 ❖ Выполнение команды gc t e s t


S go te st
PASS
ok. g o -in -p ractice/ch ap terl/h ello C-012s

Если метод getName вернет строку, отличную от "World!", результат


будет иным. В следующем примере система тестирования сообщает
о месте, где теет выявил ошибку, имя теста, имя файла с тестом и по­
мер строки с ошибкой. Для следующего примера мы изменили метод
getName, чтобы он возвращал строку, отличную от "World!".
Листинг 1.13 •> Ошибка, выявленная при тестировании командой go t e s t
S до t e s t
— FAIL: TestName (0.00 seconds)
36 Введение в язык Go

h e l l c _ t e s t . g o : 9: Response from get.Name i s unexpected value


FAIL
e x it statu s 1
FAIL g c-in -p ractice /ch a p te rl/h ello 0.010s

В состав Go входят ясе необходимые инструменты для тестиро­


вания. Более того, Go сам использует их. Для тех, кому потребуется
что-то еще, например средства поддержки разработки, основанной на
поведении (Behavior-Driven Development., B D D ), или нечто другое,
что можно найти в других языках, существуют внешние пакеты, рас­
ширяющие встроенные функциональные возможности. В Go-тестах
можно использовать весь спектр возможностей языка, включая па­
кеты.

Охват кода
Помимо выполнения тестов, система тестирования поддерживает
создание отчетов с полной детализацией охвата кода тестами, вплоть
до уровня инструкций, как показано на рис. 1.14.
ПРИМЕЧАНИЕ В версии Go 1.5 команды получения оценки охвата
тестам и стали частью ядра Go. Д о версии 1.5 команда cover о тно­
силась к дополнительны м инструментам.

Чтобы получить оценку охвата тестами, выполните следующую


команду:
s go t e s t -cover

Добавление флага -cover в команду go t e s t заставит ее вывести ин­


формацию об охвате тестами вместе с прочими сведениями о выпол­
нении тестов.

Листинг 1.14 •> Тестирование с получением сведений об охвате тестами


s go t e s t -cover
PASS
Coverage: 33.31 of statem ents
ok q o -in -p ractice/ch ap terl/h ello 0.011s

Но предоставлением этой информации система оценки охвата


кода не ограничивается. Сведения об охвате можно экспортировать
в файлы для использования другими инструментами. Также эти от­
четы можно отображать с помощью встроенных средств. Н а рис. 1.4
показано, как выглядит отчет в веб-браузере, содержащий данные
о тестируемых инструкциях.
Примеча i елычые особенное i и чаыка Go ❖ 37

Е Д Я У Н Я Я Я ГД М КИ М n o t tra c k e d n o t covered covered

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)
>

Рис. 1.4 ❖ Отчет об охвате кода тестами в веб-браузере

Обычно такие отчеты содержат данные с детализацией, вплоть до


строки. Простым примером служат инструкции i f и else. Среда Go
покажет, какие инструкции были выполнены, а какие остались не
охваченными тестированием.
СОВЕТ Более полную информацию об инструменте cover можно
найти в блоге языка Go: http://blog.golang.org/cover.

Тестированию в языке Go придается большое значение, и мы еще


уделим время этой стороне программирования в главе 4.

Форматирование
Как предпочтительнее оформлять отступы в исходном коде, сим­
волами табуляции или пробелами? Вопросы форматирования и сти­
ля затрагиваются и обсуждаются всякий раз, когда речь заходит о со­
глашениях оформления кода. Сколько можно было бы сэкономить
времени, отказавшись от этих дебатов? Пользователям языка 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

точно запустить команду go fmt в корневом каталоге пакета, чтобы Go


обошел все файлы .дс в пакете и привел их в соответствие с канони­
ческим стилем. В команде go fmt можно дополнительно указать путь
к пакету или . / . . . (для обхода всех подкаталогов).
Многие редакторы поддерживают автоматическое форматирова­
ние через встроенные средства или в виде дополнительных пакетов.
К ним относятся: Vim, Sublime Text, Eclipse и многие другие. Напри­
мер, пакет GoSublime для редактора Sublime Text производит форма­
тирование файла при его сохранении.

1.3. М есто язы ка Go среди д ругих язы ков


програм м ирования
Популярный репозиторий GitHub хранит проекты, написанные на
сотнях разных языков. Рейтинговый список ТТОВЕ языков про­
граммирования показывает снижение популярности наиболее рас­
пространенных языков. Это дает шанс другим языкам. Давайте по­
смотрим, какое место занимает Go среди множества других языков
программирования.
Изначально Go разрабатывался как системный язык. То, что часто
называют облачными вычислениями, обычно относят к одной из форм
системного программирования. Язык Go был разработан с учетом
особенностей систем, в которых он должен был использоваться.
Системные языки должны обладать определенными особенностя­
ми. Например, Go можно применять в случаях, где обычно использу­
ются языки С или C + + , но он не годится для встраиваемых систем.
Среда выполнения Go обладает системой сборки мусора, которая не
будет нормально работать во встраиваемых системах с ограниченны­
ми ресурсами.
Сравнение Go с другими популярными языками программиро­
вания позволит определить его положение по отношению к этим
языкам. Мы считаем, что Go оптимально подходит для разработки
определенных приложений, но не собираемся вести дебаты о выборе
языка программирования. Для правильного выбора необходимо учи­
тывать не только характеристики языков.

1.3.1. С и Go
Язык Go с самого начала рассматривался как альтернатива языку С
для разработки приложений. Поскольку источником вдохновения
Мес I о языка Go среди дру| их языков i ipor раммирования ❖ 39

при разработке этого языка был язык С (С по-прежнему остается


одним из популярных языков, если не самым популярным), имеет
смысл рассмотреть сходства и различия этих языков.
Программы на обоих языках - Go и С - компилируются в машин­
ный код целевой операционной системы и архитектуры. Оба языка
схожи по своему стилю, по Go далеко выходит за рамки языка С по
своим возможностям.
Язык Go имеет среду выполнения, поддерживающую такие функ­
ции, как управление потоками выполнения и сборка мусора. Разра­
батывая приложения па языке Go, вы освобождаетесь от обязанности
управлять потоками выполнения и выполнять операции по сборке
мусора, как это принято в других языках, оснащенных сборщиком
мусора. В языке С управление потоками и памятью возлагается па
программиста. Вся работа с потоками и связанные с ними действия
выполняются приложением. А для работы с памя тью сборщик мусора
в принципе не предусмотрен.
Язык С и его объектно-ориентированные производные, такие как
C ++, обеспечиваю! разработку весьма широкого спектра приложе­
ний. На языке С можно писать высокопроизводительные встраивае­
мые системы, крупномасштабные облачные и сложные настольные
приложения. Язык Go в основном предназначен для использования
в качестве системного языка и языка создания облачных платформ.
Преимуществом Go является высокая продуктивность.
Среда выполнения и набор инструментов Со доступны изна­
чально. Их возможности позволяют очень быстро разрабатывать
приложения и тратить на их создание гораздо меньше усилий, чем
требуется для написания сопоставимого приложения на языке С. На­
пример, для использования всех четырех ядер процессора па сервере
Go-нриложение может использовать сопрограммы. В аналогичном
приложении на С придется вдобавок написать код, запускающий по­
токи выполнения, управляющий их работой и выполняющий пере­
ключение между ними.
Компиляция приложений на С может занимать продолжитель­
ное время. Это особенно верно при наличии внешних зависимостей
и необходимости их компиляции. Высокая скорость компиляции
Go-приложений была одной из целей разработки языка Go, и она вы­
полняется быстрее, чем компиляция кода на языке С. Когда прило­
жение разрастается до таких размеров, что его компиляция начинает
занимать минуты или даже часы, экономия на времени компиляции
может существенно повлиять па производительность разработки.
40 Введение в язык Go

Компиляция G o-приложений выполняется настолько быстро, что


компиляция многих приложений и пакетов их зависимостей занима­
ет считанные секунды или даже еще меньше.

С + 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

приложения на сервере достаточно установить один файл. Чтобы


развернуть Ja v a -приложение, вместе с самим приложением требуется
установить и настроить среду выполнения Java.

Java-приложение компилируется в файл, который


Java-приложение -----может выполняться в любой системе
с установленной средой выполнения Java
Для Java-приложения запуска на сервере
Среда выполнения
Java — или в системе необходимо установить среду
выполнения Java (Java Runtime environment, JRE)
Операционная система

Go-приложение компилируется для операционной


Go-приложение системы и развертывается на сервере
-----или в системе. Отсутствует необходимость
в установке любых зависимостей времени
Операционная система выполнения

Р и с. 1.5 ❖ Выполнение програм м на язы ках Java и Go


в операционной си сте м е

Другое ключевое различие между языками Ja v a и Go заключается


в самом порядке выполнения приложения. Программы на языке Go
компилируются в двоичный код и выполняются операционной си­
стемой. Ja v a -приложения выполняются виртуальной машиной (V M ),
обычно содержащей динамический компилятор (Just-In-Time, JIT ).
J I T -компилятор способен анализировать выполнение кода в текущем
контексте и оптимизировать его.
Возникает важный вопрос: что работает быстрее - код, выпол­
няемый виртуальной машиной с J I T -компилятором, или предвари­
тельно скомпилированный двоичный код? Ответ па него не прост,
поскольку многое зависит от J I T -компилятора, выполняемого кода
и многого другого. Сравнительные тесты программ с аналогичной
функциональностью не выявили явного лидера.

1.3.3. Python, РНР и Go


Python и РН Р - два наиболее популярных динамических языка.
Python превратился в один из самых популярных языков, изучаемых
и используемых в университетах. Его можно применять в различных
целях, от создания облачных приложений до разработки веб-сайтов
и утилит, запускаемых из командной строки. Р Н Р - один из самых
популярных языков программирования для создания веб-сайтов.
Хотя эти два языка имеют множество различий, между ними наблю­
42 Введение в язык Go

даются определенные сходства, подчеркивающие некоторые из под­


ходов, используемых в Go.
Python и РН Р - языки с динамической типизацией, в то время как
Go является языком со статической типизацией, поддерживающим
некоторые черты, характерные для динамической типизации. Ди­
намические языки осуществляют проверку типов данных во время
выполнения и даже выполняют их преобразование на лету. В языках
со статической типизацией проверка типов данных выполняется на
основании статического анализа кода. Язык Go также поддерживает
преобразование типов в определенных границах. В некоторых случа­
ях переменные одного типа могут преобразовываться в переменные
другого типа. Это может выглядеть необычным для языка со статиче­
ской типизацией, но иногда очень удобно.
Обычно, когда РНР и Python используются для создания веб­
сайтов или приложений, они располагаются за веб-сервером, таким
как Nginx или Apache. Веб-браузер подключается к веб-серверу, обра­
батывающему соединения, а веб-сервер передает информацию среде
выполнения и программе, написанной на этих языках.
Go имеет встроенный веб-сервер, как показано на рис. 1.6. Та­
кие приложения, как веб-браузеры, подключаются напрямую к Go-
приложонию, и оно само управляет соединением. Это обеспечивает
более низкоуровневое управление и взаимодействие с подключив­
шимися приложениями. Веб-сервер, встроенный в Go, в состоянии
параллельно обрабатывать множество соединений, используя все
функциональные преимущества языка.

Процесс
Запрос к приложению на Python
или РНР поступает на веб-сервер. Веб­ Процесс
Затем веб-сервер передает его сервер
одному из процессов

Процесс

Операционная система

Запросы от клиентов поступают


непосредственно в Go-приложение.
Необходимость в промежуточном Go-приложение
приложении отсутствует.
Go-приложение самостоятельно
управляет потоками выполнения Операционная система

Р ис. 1.6 ❖ Прохождение запросов о т кл и е н то в к приложениям


на Python, РНР и Go
Место языка Со среди других языков программирования ❖ 43

Одно из преимуществ размещения приложений на Python и РН Р


за веб-сервером заключается в том, что он берет на себя обслуживание
потоков выполнения и параллельных подключений. В Python преду­
смотрена глобальная блокировка интерпретатора, позволяющая од­
новременно выполняться только одному потоку. РНР-приложение,
как правило, выполняется от начала до конца процесса. Для одновре­
менного доступа к приложению нескольких клиентов перед прило­
жением должен находиться веб-сервер, запускающий обработку со­
единений в отдельных процессах.
Для конкурентного обслуживания подключений веб-сервер,
встроенный в Go, использует сопрограммы. Среда выполнения Go
распределяет сопрограммы между потоками выполнения. Более по­
дробно об этом рассказывается в главе 3. Для обслуживания несколь­
ких соединений запускается несколько процессов с приложениями
на Python и РНР, но G o-нриложение использует одну общую среду,
что позволяет совместно использовать ее ресурсы там, где это имеет
смысл.
Внутренняя реализация Python и РН Р написана на С. Их встроен­
ные объекты, методы и функции, реализованные на С, выполняются
быстрее, чем объекты, методы и функции приложений. Исходный
прикладной код преобразуется в промежуточную интерпретируемую
форму. Один из советов по повышению производительности кри­
тических участков кода, написанных на Python и РНР, заключается
в переводе их на язык С.
G o-приложения компилируются в выполняемый двоичный код. То
ес ть программный код из стандартной библиотеки и из приложения
компилируется в машинный код. Между ними нет никакой разницы.

1.3.4. JavaScript, Node.js и Go


Язык JavaScript был создан практически за 10 дней. Он стал одним из
самых популярных языков благодаря встраиванию во все основные
веб-браузеры. В последнее время JavaScript используется на серве­
рах, в настольных приложениях и в других областях. Это стало воз­
можным благодаря платформе Node.js.
Языки Go и JavaScript, главным образом благодаря Node.js, могут
заполнять аналогичные ниши, но делают это по-разному Исследова­
ние этих различий поможет лучше разобраться в роли языка Go.
JavaScript использует одпоиоточную модель. Несмотря на под­
держку асинхронного ввода/вывода, который может выполняться
в отдельных потоках, основная программа выполняется только в од­
ном потоке. Когда выполнение кода в основном потоке требует зпа-
44 Введение в язык Со

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


кода. В Go используется многопоточная модель, где среда выполне­
ния управляет выполнением сопрограмм в отдельных потоках. Мо­
дель Go с возможностью выполнения потоков на нескольких ядрах
способна более полно использовать доступное оборудование, чем
однопоточная модель JavaScript.
Платформа Node.js использует движок V8, разработанный в ком­
пании Google и входящий в состав в Google Chrome. Движок V8 со­
держит виртуальную машину с J I T -компилятором. Концептуально
это решение схоже с Java. Виртуальная машина и J I T -компилятор
способны несколько повысить производительность. Движок V8 сле­
дит за длительностью выполнения программ и с течением времени
повышает производительность. Например, он может распознать
выполнение цикла и для увеличения производительности преобра­
зовать часть программною кода непосредственно в машинный код.
Движок V8 способен определять типы переменных, даже при том,
что JavaScript является динамически типизированным языком. Чем
дольше работает программа, тем больше движку V8 удается узнать
о пей и применить собранную информацию в целях улучшения про­
изводительности.
Программы на Go, напротив, сразу компилируются в машинный
код и выполняются со скоростью машинного кода со статической ти­
пизацией. Здесь нет нужды в J I T -компиляторе для увеличения ско­
рости выполнения. Как и в языке С, нет необходимости в применении
J IT -компи ляпии.

Работа с пакетами
Экосистема 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. Подготовка и запуск программы


на языке Go
Имеется несколько вариантов знакомства с языком Go, в зависимо­
сти от ваших предпочтений.
Самый простой способ начать работу с языком Go - пройти тур
на странице http://tour.golang.org, демонстрирующий использование
некоторых основных функций. Что выделяет тур по языку Go в ряду
типичных пособий, так это действующие примеры. Примеры можно
выполнять непосредственно в браузере. При желании можно далее
внести в них изменения и выполнить.
Поэкспериментировать с простыми G o-приложениями можно на
странице https://play.golang.org. Эта площадка для экспериментов
позволяет опробовать примеры из тура. Здесь можно проверить код
и поделиться ссылкой на него. Кроме того, на площадке можно также
опробовать примеры кода из книги.

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.4.2. Работа с Git, M ercurial и другим и систем ами


управления версиями
Для работы с пакетами и внешними зависимостями, хранящимися
в системах управления версиями, среда Go требует локальной уста­
новки этих систем. Go не дублирует инструментов управления кон­
фигурацией программного обеспечения (Softw are Configuration M an­
agement, SC M ), а просто распознает их и использует при установке.
Двумя доминирующими системами управления версиями, исполь­
зуемыми G o-разработчиками для работы с пакетами, являю тся Git
и M ercurial (hq). Система управления версиями Git очень популярна,
ею пользуются многие разработчики из Google, и создаваемые ими
пакеты хранятся в GitHub. От вас требуется лиш ь установить Git,
при этом среда Go не требует какой-то определенной версии. Подой­
дет любая из последних.

1.4.3. Знаком ство с рабочей областью


Утилита до предполагает, что исходный код на языке Go хранится
в рабочей области. Рабочая область - это иерархия, включающая ка­
талоги sre, pkg и bin, как показано в следующем листинге.

Листинг 1.15 ❖ Структура рабочей области


sgopath/ <- О Базовый каталог или SG0PATH
зге/ «- Исходный код внешних зависимостей
github.com/
Masterminds/
cooкоо/
glide/'
bin/ «- Скомпилированные программы
glide
pkg./ «- Скомпилированные библиотеки
derwin amd64/
github. сот/
Masterminds/
cookoo.a
Единственной переменной окружения, которую необходимо опре­
делить, является $G0PATH О. С ее помощью со определяет местоположе­
ние базового каталога рабочей области. Исходный код, включая ваш
собственный и всех зависимостей, должен располагаться в каталоге
src. Управляет этим каталогом разработчик, а инструменты Go помо­
гают управлять кодом, хранящимся во внешних репозиториях. Двумя
Приложение Hello Go ❖ 47

другими каталогами практически всегда управляют инструменты Go.


Если в каталоге проекта запустить команду go in sta ll, она произведет
сборку выполняемых файлов и сохранит их в каталоге bin (как показа­
но в листинге 1.15). В данном примере проект Glide будет скомпилиро­
ван в выполняемый файл glide. Для проекта Соокоо, однако, автоном­
ный выполняемый файл не будет создан. Этот проект просто содержит
библиотеки, которыми смогут пользоваться другие Go-программы.
Выполнение команды go in sta ll в этом проекте приведет к созданию
в каталоге pkg архивного файла с расширением .а.

1.4.4. Работа с переменными среды


Выполняемому файлу до требуется только одна переменная окруже­
ния - G0PA.H. Она должна хранить путь к каталогу рабочей области,
куда будут импортироваться пакеты, сохраняться выполняемые фай­
лы и промежуточные архивы. Следующий пример демонстрирует
создание рабочей области в каталог е до в U N IX -подобной системе:
S mkdir $H0ME/go
S export G0PATH=$H0ME/go

Внутри каталога, на который указывает переменная среды G0PATH,


программа до создаст каталог bin для размещения выполняемых файлов.
Для большего удобства желательно добавить этот каталог в переменную
среды PATH. Например, в U N IX-подобных системах это делается так:
S export PATH=$PATH:$GOPATH/bin

Если вы пожелаете установить двоичные файлы в альтернатив­


ный каталог, определите путь к нему в переменной окружения GOBIN.
Определять эту переменную необязательно.

1.5. Приложение Hello Со


В следующем листинге представлена очередная вариация стандарт­
ной программы «Ilello World», которая выводит через веб-сервер
фразу Hello, шу name is Inigo Montoya.

Л и сти н г 1 .16 ❖ В ы в о д H e llo W o rld ч е р е з в е б -с е р в е р : in ig o .go


package main « - О Приложение использует пакет main

import ( © Импорт необходимых пакетов


"fmt"
"n et/h ttp "
48 В зедение в язы к Go

func h ello (res http.ResponseWriter, req * http.Request) Обработка


fmt.Fprir.t (res, "Hello, my name is Inigo Montoya") НТТР-запроса

func main() { © Основная логика приложения


http.HandleFuncf"/", hello)
h ttp . ListenAndServe("lo calh o st: 4 СС C", n il)

Это простое приложение состоит из трех частей. Оно начинается


с объявления пакета О. В отличие от библиотек, краткие имена ко­
торых описывают их назначение, например net или crypto, данное
приложение называется main. Для вывода строк и работы в режиме
веб-сервера импортируются пакеты fnt и hrtp ©. Импорт пакетов обес­
печивает их доступность в коде и в скомпилированном приложении.
Выполнение приложения начинается с вызова функции main ©, не
имеющей аргументов и не возвращающей значений. В первой ее стро­
ке вызывается функция http. HandleFunc, сообщающая веб-серверу, что
при обращении к пути / должна вызываться функция he 11с. Функция
hellc реализует типичный интерфейс обработчиков. Она получает
объекты с H T TP-запросом и H T TP-ответом. Затем следует вызов
метода http .ListenAndServe, который запускает веб-сервер, принимаю­
щий запросы на порту 4000 домена iccalhost.
Запустить это приложение можно двумя способами. В следующем
листинге используется команда go run, компилирующая приложение
в каталог temp и запускающая его.

Листинг 1.17 ❖ Запуск на вы полнение файла inigo.go


S до run inigo.go

Временный файл автоматически удаляется после завершения при­


ложения. Это удобно при разработке постоянно тестируемых новых
версий приложений.
После запуска приложения можно открыть веб-браузер и перейти
по адресу; http://localhost:4000, чтобы посмотреть ответ, как показано
на рис. 1.7.
Приложение можно также собрать и запустить другим способом,
как показано в следующем листинге.

Листинг 1.18 ❖ Сборка inigo.go


S go build inigo.go
S ./in ig e
• ®• < О local iosi:4Q00 С ± э О
HolLo, my name is Inigo Montoya

Рис. 1.7 •> Просмотр вывода


«Hello, my name is Inigo Montoya» в веб-браузере

Здесь сначала выполняется сборка приложения. Если вызвать ко­


манду go build без имен файлов, она соберет исходный код в текущем
каталоге. Если указать одно или несколько имен файлов, она скомпи­
лирует только указанные файлы. Затем собранное приложение мож­
но запустить.

1.Б. И тоги
Язык Go предназначен для работы на современном аппаратном обе­
спечении и разработки современных приложений. Он использует
последние технологические достижения и инструменты, упрощаю­
щие процесс разработки. В этой главе мы рассмотрели основные до­
стоинства языка программирования Go и познакомились:
О с философией языка Go, заключающейся в простоте и расши­
ряемости, позволившей создать удобный язык и окружающую
его экосистему;
О с особенностями языка Со, помогающими использовать пре­
имущества современного оборудования, такими как сопро­
граммы, обеспечивающие параллельное выполнение;
О с сопровождающим язык Go набором инструментов, включаю­
щих средства тестирования, управления пакетами, форматиро­
вания и документирования;
О с характеристиками Go в сравнении с такими языками, как С,
Java, JavaScript, Python, РН Р и другими, а также узнали, для
каких задач он подходит лучше всего;
О с порядком установки и использования Go.
50 Введение в язык Go

Глава 2 начинается с рассмотрения основ, необходимых для созда­


ния самых разны х приложений, от консольных утилит до веб-служб.
Как приложения должны обрабаты вать команды, аргументы и ф л а­
ги? Как правильно заверш ать работу веб-служ б? О тветы на подоб­
ные вопросы заклады ваю т основу для создания полноценных при­
ложений.
Далее в книге рассматриваются практические аспекты создания
и выполнения приложений, написанных на язы ке Go. Главы будут
дополнять друг друга, пока не будет достигнут кульминационный
момент, когда, объединив все рассмотренные приемы, мы создадим
законченное приложение.
Глава

Надежная основа

В эт ой главе рассм ат риваю т ся следую щ ие т ем ы :


О использование, флагов, парам ет ров и аргум ент ов командной строки;
О передана, наст роек в прилож ение;
О за п уск и ост ановка веб-сервера;
О управление мартрут изац ией для служб веб-серверов.

Создание основы приложений - не менее важный шаг, чем любой


другой. Заверш ение работы приложения, позволяю щ ее не потерять
данных и не вы звать трудностей в работе с ним, является примером,
для чего предназначена надежная основа.
Э та глава охваты вает четыре основных вопроса. О на начнется
с рассмотрения консольных приложений, известны х также как CJJ-
приложения. Здесь вы познакомитесь со способами обработки пара­
метров командной строки, иногда назы ваем ы х флагами или опциями,
принятыми в современных приложениях для Linux и других P O S IX -
совм естим ы х системах. В процессе обсуждения вы исследуете н а­
стройки, позволяю щ ие сосредоточиться на разработке кода консоль­
ных приложений, а не на особенностях его организации.
Затем будет рассмотрено несколько способов передачи конф игура­
ционной информации в приложения, вклю чая популярные форматы
ф айлов и данных, используемые для хранения конфигурационной
информации.
Далее последуют серверы и практические приемы их запуска
и остановки. Эти действия могут показаться элементарными, но
разреш ение определенных ситуаций на ранних этапах (как, напри­
мер, остановка сервера до заверш ения сохранения данных) поможет
уменьш ить количество будущих проблем.
Глава заканчивается обсуждением способов сопоставления путей
на серверах. Сопоставление путей с U R L -адресами - типичная за-
52 Надежная основа

дача веб-сайтов и серверов, решаемая средствами прикладною про­


граммного интерфейса передачи репрезентативных состояний (Rep­
resentational State Transfer, REST). Здесь вы узнаете, как реализовать
несколько общепринятых методов сопоставления путей.
К концу этой главы вы познакомитесь с практическими приемами
создания клиентов командной строки и веб-серверов, что послужит
основой для падежных Go-приложений.

2.1. Ш -прилож ения на Со


При использовании консольного приложения (такого как система
управления версиями Git, приложение для работы с базой данных
MySQL или веб-сервер Apache) аргументы и флаги командной стро­
ки являются частью его пользовательского интерфейса. Стандартная
библиотека Go содержит встроенные средства для их обработки.

Оконные приложения
Go, как системный язык, не имеет встроенной поддержки для созда­
ния оконных приложений, подобных тем, что используются в Microsoft
Windows или Mac OS X. В Go-сообществе велись эксперименты по их
разработке, но они не имели четкой направленности.

2.1.1. Флаги командной строки


Стандартная библиотека Go включает средства обработки аргумен­
тов и флагов, реализованные в стиле операционной системы Plan 9,
который отличается от стиля, принятого в системах, основанных на
GNU/Linux и Berkeley Software Distribution (BSD ), таких как Mac
OS X и FreeBSD. Например, в Linux- и B SD -системах можно ис­
пользовать команду Is - la для вывода списка всех файлов в ката­
логе. Часть -1а команды содержит два флага, или параметра. Флаг
1 указывает команде Is использовать подробную форму вывода,
а флаг а определяет необходимость включения в список скрытых
файлов. Система обработки флагов в языке Go не позволяет объ­
единять несколько флагов и рассматривает такие флаги как один
флаг с именем 1а.
Команды в стиле GNU (например, команда Is в Linux) поддержи­
вают длинные параметры (например, —color), содержащие два дефи­
са, чтобы указать, что строка color - это не пять, а один параметр.
CU-приложения Hd Go ❖ 53

В сети ведутся жаркие дебаты по вопросу преимуществ системы


Go (или Plan 9) перед стилем, доминирующим в GNU, Linux и BSD.
Честно говоря, здесь все определяется личными предпочтениями.
Двойные дефисы в GNU-стиле выглядят достаточно неуклюже и час­
то приводят к ошибкам. Длинные опции с одним дефисом, принятые
в языке Go, исключают возможность (которая только что упомина­
лась) группировки односимвольных параметров.

О перационная систем а Plan 9


Операционная система Plan 9, разработанная в Bell Labs как преем ­
ница UNIX, является операционной системой с открытым исходным
кодом и разрабатывалась в период между 1980 и 2002 годом. Двое
из создателей языка Go - Кен Томпсон (Ken Thompson) и Роб Пайк
(Rob Pike) - входили в начальную команду и сыграли заметную роль
в разработке Plan 9. Это и привело к появлению сходных черт в двух
проектах.

Встроенная система обработки флагов не различает короткие


и длинные флаги. В ней флаг может быть как коротким, так и длин­
ным, и каждый флаг должен отделяться от других. Например, если
выполнить команду go help build, она выведет список флагов, таких
как -V, -race, -х и -work. Каждому параметру соответствует только
один вариант написания, а не длинное или короткое его имя.
Для иллюстрации обработки флагов по умолчанию в следующем
листинге демонстрируется консольное приложение, использующее
пакет flag.
Листинг 2.1 ❖ CLI-приложение Hello World, использующее пакет flag
s flag_cli
Hello World!
s flag_cli -s -name Buttercup
Hola Buttercup!
s flag_cli --Spanish -name Buttercup
Hola Buttercup!

Все флаги отделены друг от друга пробелами и начинаются с одно­


го или с двух дефисов (- или —), которые считаются взаимозаменяе­
мыми. Существующий метод позволяет определять флаги с коротки­
ми или длинными именами, но, как показано в следующем листинге,
этот метод действует неявно.
54 Надежная основа

Листинг 2.2 ♦> Исходный код приложения Hello World, использующ его
пакет flag: flag_cli.go
package ir.ain
inport (
"flag" <- Импорт стандартного пакета flag
О Создание новой
I переменной из флага

var name = flag.String("name", "World", "A name to say hello to .")

var Spanish bool ч- в Новая переменная для хранения значения флага

func i n i t () {
flag.EoclVar(Sspanish, "spanish", fa ls e , "Use Spanish language.")
flag.BoolVar (Sspanish, " s " , "a lse , "Use Spanish language.")

© Присваивание значения флага переменной


func main() {
flag. Far se () ч - О Парсинг флагов и установка соответствующих значений в переменных
i f Spanish == true {
fm t.Printf C'Hola %s!\n", *name) ч - © Доступ к name как к указателю
} else {
fm t.Printf ("Hello %s!\n", *r.ame)
}

Здесь показаны два способа определения флагов. Первый позво­


ляет создать переменную на основе флага. В данном примере для
этого используется метод flag.String() О. Методу flag.String пере­
даются: имя флага, значение по умолчанию и описание аргументов.
В переменную папе записывается адрес значения флага. Для доступа
к этому значению необходимо использовать переменную паше как
указатель 0 .
Второй способ обработки флагов позволяет определить длинное
и короткое имена флага. Сначала создается обычная переменная ©
того же типа, что и значение флага. Она будет использоваться для
хранения значения флага. Затем вызывается одна из функций в паке­
те flag, присваивающая значение флаг а существующей переменной 0 .
В данном случае метод flag.BoolVar вызывается дважды, один раз для
длинного имени флага и один раз для короткого.
СОВЕТ Для флагов каждого типа существует своя функция. О зна­
комиться с ними можно в описании пакета flag (h ttp ://g o la n g .o rg /
pkg/flag).
CLI-приложения Hd Go ❖ 55

И наконец, чтобы присвоить значения флагов переменным, необ­


ходимо вызвать метод flag. Parse ( ) О.
СОВЕТ Аргументы ком андной строки не регистрирую тся пакетом
flag, но к ним м ожно получить доступ с помощ ь ф ункций Args или Arg
из пакета flag.

Пакет flag не создает текст справки с описанием флагов, но помога­


ет в его создании. Для этого в пакете имеются две удобные функции.
Функция PrintD efaults генерирует текст с описанием флагов. Напри­
мер, строка с описанием параметра паше О будет содержать текст:
-папе strin g
A nair.e to say hello to . (defaulr "World")

Такая аккуратность языка Go помогает информировать пользова­


теля об особенностях использования программы.
Кроме того, в пакете flag имеется функция V isitA ll, принимающая
функцию обратного вызова в качестве аргумента. Она выполняет об­
ход всех флагов и для каждого вызывает указанную функцию, что
позволяет па ходу изменить текст с описанием. Например, следую­
щий фрагмент выведет текст: -паше: А паше to say h e llo to . (Default:
'World').

Листинг 2.3 ♦> У становка нестандартного текста справки для ф лага


flag.V isitA ll (furic (flag *flag.Flag) {
format := " \ t - l s : %s (Default: '%s 1) \n"
fm t.P rintf(form at, flag.Name, flag.Usage, flag.DefValue)

РЕЦЕПТ 1 Аргум енты командной строки в стиле GNU/UNIX


Встроенный пакет flag несомненно полезен и обеспечивает самые
необходимые возможности, но он не поддерживает предоставления
флагов, которое ожидает найти большинство пользователей. Разни­
ца в стилях взаимодействия с пользователями в Plan 9 и L in u x/B SD
достаточно значительна, чтобы вызвать непонимание. В основном
э го связано с появлением проблем при попытке совместить короткие
флаги, как в команде Is -la.
Проблем а
Те, кто работает в системах, отличных от Windows, обычно исполь­
зуют один из вариантов UNIX и ожидают обработки флагов в стиле
UNIX. Как написать на языке Go инструмент командной строки, соот­
56 Надежная основа

ветствующий ожиданиям пользователей? Идеальным решением ста­


нет написание одноразовой специализированной обработки флагов.
Реш ение
Эту часто встречающуюся проблему можно решить несколькими
способами. Некоторые приложения, такие как система управления
контейнерами программного обеспечения Docker, имеют субпакеты,
содержащие код для обработки флагов в стиле Linux. В некоторых
случаях применяются разновидности пакета flag языка Go с дополни­
тельными возможностями. Но реализация извлечения аргументов, от­
дельная для каждого проекта, требует достаточно большого дублиро­
вания усилий для многократного решения одной и той же проблемы.
Лучше всего для этих целей использовать одну из существующих
библиотек. Можно импортировать в приложение несколько автоном­
ных пакетов. Многие из них основаны на пакете flag из стандартной
библиотеки и имеют совместимые или аналогичные интерфейсы.
Импортировать и использовать один из этих пакетов гораздо проще,
чем вносить изменения в пакет flag и обслуживать его в дальнейшем.
Обсуж дение
В следующих примерах вы познакомитесь с двумя такими пакета­
ми, реализующими немного отличающиеся подходы. Первый из них
является попыткой сохранить совместимость с интерфейсом пакета
flag, в то время как второй отказывается от нее.
Пакет GNUFLAG
Пакет launchpad.net/gnuflag, похожий на flag, реализует обработку
флагов в стиле GNU (Linux). Но не позволяйте ввести себя в заблуж­
дение. Его лицензия отличается от лицензии G PL, на основе которой
обычно распространяются библиотеки и программы, основанные на
GNU, и больше напоминает лицензию в стиле B SD или лицензию Go.
Пакет gnuflag поддерживает несколько видов флагов, включая сле­
дующие:
О - f - односимвольные, или короткие, флаги;
О -fg - группы односимвольных флагов;
О --flag - многосимвольные, или длинные, флаги;
О --flag х - длинные флаги с аргументами;
О - f х или -fx - короткие влаги с аргументами.
Пакет gnuflag имеет практически тот же интерфейс, что и пакет flag,
с одним заметным отличием. Его функция Parse имеет дополнитель­
ный аргумент. В остальном их интерфейсы полностью идентичны.
CLI-приложении на Go ❖ 57

Пакет Лад обрабатывает флаги между именем команды и первым ар­


гументом, не являющимся флагом. Например, фрагмент в листин­
ге 2.4 извлекает флаги -s и -п, последний из которых имеет аргумент.
Когда пакет flag обнаруживает аргумент foe, он прекращает дальней­
ший парсинг командной строки. Это означает, что флаг -bar не будет
обработан. Пакет gnuflac позволяет продолжить анализ и извлечь фла­
ги, следующие за аргументом.
Л и с т и н г 2.4 ❖ Извлечение флагов пакетом flag
s flag_based_cli -s -п Buttercup foe -bar

Чтобы перейти на использование пакета gnuflag, достаточно изме­


нить в приложении две вещи. Во-первых, изменить инструкцию им­
порта. И во-вторых, добавить в функцию Parse дополнительный пер­
вый аргумент со значением true или false. Если передать значение
true, Parse выполнит поиск флагов по всей командной строке, а если
передать значение false, пакет gnuflag будет действовать в точности,
как пакет flag. Это все.
ПРИМЕЧАНИЕ Сайт lanuchpad.net использует систему управления
версиями Bazaar. Вам придется установить ее, чтобы Go см ог и з­
влечь пакет. Более подробную информацию можно найти на стра­
нице: http://bazaar.canonical.conn/.

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 Надежная основа

Следующий листинг демонстрирует использование этого пакета.


Ои содержит код консольного приложения «Hello World», адаптиро­
ванный иод использование пакета gc-flags.
Листинг 2.5 ❖ Использование пакета go-flags
package main import (

"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 ) <- © Извлечение значений флагов в структуру

i f c p t s . S p a n is h == t r u e { © Использование значений флагов


f m t . P r i n t f ("H ola % s ! \ n " , opes.Name) через свойства структуры
} else {
f m t . P r i n t f ( " H e l l o % s ! \ n " , opes.Name)
}

Первое существенное отличие от предыдущих методов заключает­


ся в определении флагов. В данном случае флаги определяются как
свойства структуры О . Имена свойств обеспечивают доступ к значе­
ниям флагов. Сведения о флаге, такие как краткое или длинное имя,
значение по умолчанию и описание, определяются парами ключ/
значение, следующими за именем свойства структуры. Парсинг этих
пар реализован с применением пакета reflect из стандартной библи­
отеки. Например, свойству opts .Name соответствуют: флаг с коротким
именем -п, и длинным именем --name, значение по умолчанию World
и текст описания "A name to say hello то.".
Чтобы сделать значения доступными через структуру opts, необхо­
димо вызвать функцию Parse и передать ей структуру opts © После
этого значения флагов (или их значения по умолчанию) будут до­
ступны через свойства структуры ©.
CLI-приложения Hd Go ❖ 59

2.1.2. Фреймворки командной строки


Обработка флагов - не единственный важный аспект создания при­
ложений командной строки. Go-разработчики конечно же знают, как
реализовать функцию main и писать простые программы, но при этом
им часто приходится снова и снова писать один и тот же набор воз­
можностей. К счастью, существуют инструменты, предоставляющие
удобную основ)'' для создания программ командной строки, и в этом
разделе мы познакомимся с одним из них.

РЕЦЕПТ 2 Как избежать написания повторяющегося кода


в CLI-приложениях
Представьте, что вы получили задание написать быстрый инструмент
командной строки для несложной обработки. Вы начинаете создавать
его с нуля, добавляя уже существующий стереотипный код. И он ра­
ботает. К сожалению, он работает настолько хорошо, что возникает
желание написать более продвинутую версию, принимающую новые
параметры. Но, чтобы добавить новые возможности, требуется мо­
дернизировать существующий стереотипный код и включить в него
поддержку новых требований. Затем цикл повторяется.
Проблема
Приходится неоднократно писать однотипные программы команд­
ной строки. Сначала они задумываются как одноразовые, но некоторые
из них в конечном итоге разрастаются далеко за пределы ожидаемых
изначально возможностей. Зачем повторять один и тот же стереотип­
ный код с последующей реорганизацией верхнего уровня программы?
Существуют ли инструменты, реализующие простой шаблон много­
кратного применения для быстрого создания СЛЛ-программ?
Реш ен и е
Если вам требуется удобный и полный набор возможностей для
создания консольного приложения, обратите внимание па фреймвор­
ки, которые в дополнение к обработке флагов обеспечивают марш­
рутизацию команд, вывод справочного текста, поддержку подкоманд
и функцию автозавершения в командной строке. Одним из самых
популярных фреймворков для создания консольных приложений
является c li ( https://github.com/urfave/cli). Он используется в таких
проектах с открытым исходным кодом, как Cloud Foundry) система
управления контейнерами Docker, система непрерывной интеграции
и развертывания Drone.
60 Надежная основа

Обсуж дение
Сочетающий в себе поддержку маршрутизации, парсинг флагов,
извлечение параметров и возможность подготовки справочной доку­
ментации, фреймворк c li .до обеспечивает простейший способ разра­
ботки консольных приложений.
Простое консольное приложение
Перед знакомством с консольным приложением, поддерживаю­
щим несколько команд, имеет смысл остановится на консольном при­
ложении, осуществляющем одно действие. Вы сможете использовать
его как фундамент для расширения возможностей и поддержки не­
скольких команд и подкоманд. На рис. 2.1 изображена структура при­
ложения командной строки с одним действием.

$ арр [options] arguments

Доступ к параметрам осуществпяется Один или несколько


с помощью функций Global*. необязательных аргументов
например: GlofcalString ()

Рис. 2.1 ❖ С тр у кт у р а запуска п р о с то го приложения

В следующем листинге приводится сеанс работы с простым кон­


сольным приложением, которое выводит Hellc World! или Hello с име­
нем, указанным в командной строке.

Л и с т и н г 2.6 ❖ CLI-приложение Hello World: h e llo _ c li.g o


S h ello _ cli
He’ ’ о World!
S hello_cli —neme Tnigo
He__o Inigc!
s h ello _ cli -n Inigo
Hello Inigc!
s h ello _ cli -help
NAME:
h ellc _ cli - Print hello world
USAGE:
h ellc _ cli [global options] command [command options] [argum ents...]
VERSION:
0.0.0
COMMANDS:
CLI-приложении Hd Go ❖ 61

help, h Shows a l i s t of commands cr help for one command

GT.ORAT, OPTIONS:
—name, -n 'World' Who to say h ello to.
—help, -h show help
—version, -v p rin t "he version

При использовании фреймворка c l i . go ото приложение легко мож­


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

Л и с т и н г 2.7 ♦> CLI-приложение Hello World: h e llo c li.g o


package main

import (
"fmt"
"os"

"gopkg. in /u rf a v e / c li. v l" -с- Подключение пакета cli.go

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

app.Action = funcic *cli.C o n tex t) error { © Определение выполняемого


name := c.GlobalStrinq("name") действия
fm t.P rin tf("H ello % s!\n", name)
return n il

app.Run(os.Args) О Запуск приложения

После импорта пакета cli.g o , вызовом метода cli.NewApp создается


новый экземпляр приложения. Возвращаемая при этом переменная
является основой приложения. Настройка свойств Name и Usage необ­
ходима для хранения справочной информации О.
62 Надежная основа

СОВЕТ Если не определить значение свойства Name, фреймворк


c l i -до присвоит ему имя приложения. Такое поведение по умол­
чанию может пригодиться, если позднее приложение может быть
переименовано.

Свойство Flags является частью свойства c li.F lag , содержащего


глобальные флаги ©. Каждый флаг может иметь длинное имя, ко­
роткое ими или оба сразу. Некоторые флаги, такие как c l i . SlringFlag,
имеют свойство Value для значения по умолчанию. Ф лаг cli.BcolFlag
является примером флага, не имеющего значения по умолчанию.
При его наличии в командной строке считается, что он имеет значе­
ние true, а в отсутствие - значение false. Свойство Usage использу­
ется для хранения справочной информации, которая выводится по
запросу.
Фреймворк c l i . до позволяет определить действие по умолча­
нию, выполняемое при запуске приложения, команды и подкоман­
ды. Команды и подкоманды будут рассмотрены в следующем раз­
деле. Свойство Action экземпляра приложения определяет действие
по умолчанию ©. Ему присваивается функция, получающая *c li.
Context в качестве аргумента и возвращающая ошибку. Эта функ­
ция содержит код, выполняемый приложением. В данном слу­
чае она извлекает имя для вставки в приветствие, вызывая метод
с . GlobalStringC'nair.e") . Если потребуется вернуть ошибку, чтобы
завершить приложения с ненулевым кодом завершения, функция
должна будет вернуть объект cli.E x itE rro r, который можно создать
вызовом cli.NewExitError.
Последний шаг запуск только что созданного приложения. При
этом аргументы приложения, хранящиеся в os .Args, передаются мето­
ду Run приложения О.
СОВЕТ В теле функции Action также имеется доступ к аргументам,
список которых можно получить вызовом о.Args. Например, первый
аргумент доступен как с .Args () [(Г.

Ко м ан ды и подком анды

Приложения, использующие фреймворк c l i .до, часто поддержива­


ют команды и подкоманды, как показано на рис. 2.2. Вспомните при­
ложение Git, с помощью которого можно выполнять такие команды,
как gir add, g i t commit и g it push.
CLI-приложения на Go ❖ 63

$ арр [global options] command [command options] arguments

“ 7----------- 7------ 'ч---------------«Г-


Доступ к глобальным Выполняемая Доступ к параметрам Один
параметрам команда команды осуществляется или несколько
осуществляется с помощью контекстных необязательных
с помощью функций функций, например аргументов
Global*, например String ("name")
GlobalStringf)

$ арр [global options] command sub-command [command options] arguments

----- 1-----
Подкоманда - команда,
вложенная в другую команду

Р ис. 2.2 ❖ С тр у кту р а командной с тр о ки


для приложения с командами и подкомандами

Для демонстрации использования команд в следующем листинге


представлен код простого приложения, поддерживающего команды
прямого и обратного отсчета.

Л и с т и н г 2.8 ❖ Прямой и обратный отсчет: count_cli.go


package ir.ain

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} <- О Определение одной или нескольких команд

Name: "up", ShortName: "u",


Usage: "Count Up",
Определение © Flags: []c li.F la c {
параметров c l i . IncFlaqf
команды Name: "stop, s",
Usage: "Value to count up to ",
Value: 10,
64 Н адеж ная о с н о в а

Action: func(c *cli.C o n tex t) error (


s ta r t := c.in tC 'sto p ") <- © Получение параметра
i f s ta r t <= 0 { команды
fm t.P rin tln("Stop cannot be n egative.")
}
for i := 1; i <= s t a r t ; i++ (
fm t.P rin tln (i)
}
return n il

Name: "down", ShortName: "d",


Usage: "Count Down",
Flags: []c li.F la g {
c l i . IntFlagf
Name: " s t a r t , s ",
Usage: "S ta rt counting down from",
Value: 13,

Action: func(c *cli.C on tex t) error (


s ta r t := c .I n t ( " s t a r t " )
i f s ta r t < 0 {
frrc.Pri.ntln("Start cannot be n egative.")
}
for i := sa a r t; i >= 0; i — (
fm z.Prin tln (i)
}
return n il

},
}
app.Run(os.Argo)

В этом приложении вместо свойства Action определяется свой­


ство Commands, содержащее подробные сведения о каждой из команд
О. Свойству Action приложения фреймворк присвоит функцию по
умолчанию, которая просто выводит на экран справочную инфор­
мацию. То есть если запустить приложение без любой из поддержи­
ваемых команд, оно выведет справку. Каждая команда имеет соб­
ственные свойства Name и Action, а также необязательные свойства
О бработка конф игурсш ионной и н ф о р м ш и и ❖ 65

Flags, Usage и ShortName. Подобно тому, как основное приложение


может иметь свойство Commands со списком команд, каждая команда
также может иметь свойство Commands с перечнем подкоманд этой
команды.
Свойство Flags команды действует подобно одноименному свой­
ству приложения. Свойства Name и Usage определяются подобно со­
ответствующим свойствам приложения, как и свойство Value в не­
которых случаях. Глобальные параметры, как показано на рис. 2.2,
доступны всем командам и определяются до команд. Параметры
каждой конкретной команды определяются внутри этой команды
© Кроме того, они извлекаются из cli.C o n tex t с помощью таких
функций, как Siring, Int или Bool ©. Они напоминают функции из­
влечения глобальных флагов, отличаясь лишь отсутствием префик­
са Global.
Этот раздел начался с демонстрации отличий Go-программ ко­
мандной строки от обычных консольных программ, распростра­
ненных в U N IX -системах. Сначала мы познакомились с флагами
командной строки. Затем с библиотеками для обработки флагов
в стиле UNIX. Потом обсудили проблему более высокого уровня: как
быстро создавать приложения командной строки, без необходимости
повторно писать один и тот же стереотипный код, и познакомились
с великолепной библиотекой, помогающей решить ее. Теперь можно
перейти к теме, затрагивающей практически все программы, - к теме
конфигурирования.

2.2. Обработка конф игурационной


информации
Второй основной областью, дополняющей обработку флагов, явля­
ется сохранение наетроек приложения. Примерами могут служить
файлы в каталоге etc, в некоторых дистрибутивах Linux, и пользова­
тельские конфигурационные файлы, такие как .gitcoriig или .bashre.
Передача конфигурационной информации в приложение является
насущной необходимостью. Практически все приложения, включая
консольные и серверы, используют возможность сохранения на­
етроек. Инструменты управления конфигурациями, такие как Ап-
sible, Chef и Puppet, обладают довольно обширными возможностями
управления настройками на уровне дистрибутива. Но как передавать
и использовать конфигурационную информацию в приложениях?
66 Надежная основа

Ansible, Chef и Puppet


Ansible, Chef и Puppet - популярные инструменты управления
компьютерами и их конфигурациями. Они обычно используются для
управления программным обеспечением кластеров и настройками
работающих в них приложений. Например, возьмем приложение, вы-
пол ня ющее подкл ючен ие к базе данных. База данных может находить­
ся на том же сервере, на другом сервере или в кластере серверов.
С помощью инструментов, перечисленных выше, можно управлять
установкой и настройками этого приложения, определяя правильные
параметры подключения к базе данных.

В предыдущем разделе речь шла о передаче настроек через пара­


метры командной строки, а здесь рассматривается их передача через кон­
фигурационные файлы или, в случае 12-факторых приложений1, с по­
мощью переменных окружения. В этом разделе мы добавим поддержку
популярных форматов храпения конфигурационной информации,
включая JSO N , YAML и INI. Затем обсудим подход передачи настроек
через переменные окружения, соответствующий шаблону 12 факторов.

РЕЦЕПТ 3 Использование конфигурационных файлов


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

Одним из самых популярных форматов конфигурационных фай­


лов в настоящее время является формат Jav aS crip t Object Notation

https://12factor.net/ru/. - Прим.ред.
OOpdOotKd конфи! ypdLil/IG H H O I/l HHtJjOpMdUHH ❖ 67

( JS O N ) . С тан д ар тн ая б и б л и о тек а G o вк л ю ч ает с р ед ства а н а л и за и о б ­


рабо тки д ан н ы х в этом ф о р м ате. И это н еуди ви тельн о, п о том у что
ф ай л ы в ф о р м ате J S O N и сп о л ьзу ю тся очен ь ш ироко.

О бсуж дени е
Р ассм о тр и м J S O N -ф ай л config. j son со сл едую щ и м содерж и м ы м :

"enabled": true,
"path": "/u sr/lo cal"

В сл ед ую щ ем л и сти н ге п р ед ставл ен код, вы п ол н яю щ и й а н ал и з


это го ф ай л а.

Листинг 2.9 ❖ Анализ конфигурационного JSON-файла: json_c.onfig.go


package ir.ain
import (
"encoding/json"
":mt"
"os"

type configuration struct { О Определение структуры, соответствующей значениям


Enabled bool в JSO N-файле
Path string

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)

fmt. Frintln(conf. Path)

З д е сь м ож н о вы д ел и ть н ескол ьк о этап о в. В о-п ервы х, н еобходи м о


оп ред ел и ть тип или коллекц и ю ти пов, со о тв етству ю щ у ю стру к ту р е
J S O N -ф ай л а О . И м ен а зн ачен и й и их стр у к ту р а долж н ы с о о т в е т ­
ств о в а т ь стр у к ту р е J S O N -ф айла. Ф у н к ц и я main о тк р ы вае т к о н ф и гу ­
рацион н ы й ф ай л © и д екод и рует его сод ер ж и м ое в эк зем п л я р струк-
68 Надежная основа

туры configuration ©. Если при этом не возникнет ошибок, значения


из JS O N -файла становятся доступными через переменную conf, и их
можно будет использовать в приложении.
ПРИМЕЧАНИЕ Парсинг и обработка данных в формате JSON име­
ет много особенностей и нюансов. В главе 6, посвященной работе
с прикладным интерфейсом JSON, более подробно описываются
возможности, связанные с форматом JSON.

Хранить конфигурацию в формате JS O N удобно, если он вам хо­


рошо знаком, вы пользуетесь одним из распределенных хранилищ
конфигураций, таких как etcd, или хотите ограничиться только при­
менением стандартной библиотеки Go. Ф ай лы в формате JS O N тте
могут содержать комментариев, которые часто используются для хра­
нения дополнительных сведений или примеров. При использовании
следующих двух технологий такие комментарии доетупны.

Хранилище конфигураций etcd


Подобно Apache ZooKeeper и Doozer, etcd реализует распределенное
хранилище конфигураций для совместного использования и обнару­
жения служб. Это высокодоступное хранилище использует алгоритм
консенсуса Рафта (Raft) для управления репликациями и написано на
языке Go. Более подробную информацию о нем можно найти на стра­
нице: https://github.com /coreos/etcd.

Реш ение

Рекурсивный акроним YAM L расш иф ровы вается как YAML Ain't


Markup Language (Y A M L - не язы к разм етки) и обозначает удобо­
читаемый формат сериализации данных. Ф ор м ат YAM L легко чи­
тается, может содержать комментарии, и с ним достаточно просто
работать. Ф о р м ат YA M L широко используется для хранения кон­
фигураций приложений, и мы, как авторы, рекомендуем его. В я зы ­
ке Go отсутствует встроенный YA M L-процессор, но сущ ествует
несколько доступных сторонних библиотек, и одна из них рассм ат­
ривается далее.
О бсуж дение
Рассмотрим простой конфигурационный файл в формате YAML:
I Сорока комментария
enabled: true
path: /usr/local
О бработка конф игурсш ионной и н ф о р м ш и и ❖ 69

Следующий листинг демонстрирует пример извлечения и вывода


конфигурационных данных из YAML-файла.

Л и с т и н г 2.10 ❖ Анализ конфигурационного YAML-файла: yaml_config.go


package train

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)

fmt. Frintln (config.Get ("path")) © Вывод значений из YAM L-файла


fmt. F rin tln (config. GetBool("enabled"))

Для работы с YAML-файлами представленный в листинге код им­


портирует пакет github.com/kylelemons/go-gypsy/yaml О. Этот пакет,
который мы сами используем и рекомендуем его вам, позволяет чи­
тать данные в формате YAML из строки или из файла ©, поддержи­
вает различные типы данных и обеспечивает доступ к конфигураци­
онным данным в формате YAML.
Функция ReacFile читает файл конфигурации и возвращает струк­
туру File, объявленную в пакете yaml. Эта структура открывает доступ
к данным из YAML-файла. С помощью метода Get можно получить
строковое значение ©. Значения других типов, например логические,
можно получить с помощью таких методов, как GetBool. Для каждого
тина данных имеется свой метод, что позволяет гарантировать над­
лежащую обработку и возврат значений нужных типов.
Реш ен и е
Файлы в формате INI широко используются уже несколько деся­
тилетий. Это еще один формат, который могут использовать прило­
жения на языке Go. Хотя разработчики языка Go не включили его
поддержку в состав языка, существуют сторонние библиотеки для
удовлетворения этой потребности.
Обсуж дение
Рассмотрим следующий INI-файл:
70 Надежная основа

; Сорока комментария
; Section]
enabled = true
path = /usr/local if еще один кошентарий
Ф р агм ен т н следующ ем листинге извлекает и выводит данные из
этого файла.

Л и с т и н г 2.11 ❖ Извлечение данных из конфигурационного INI-файла:


ini_c onfig. go
package ir.ain

import (
"fltlt."
"gopkg. i г/gcfg. v1" +- О Импорт пакета для работы с INI-файлами

tunc main() {
config := struct ( © Создание структуры для хранения
Section struct { конфигурационных значений
Enabled bool
Path string

err := gcfg.ReadFilelnto(Sccnfig, "co n f.in i") © Извлечение


i f err != n il { данных из INI-файла
fm t.Prin tln ("Fai led to parse config file: %s", err) в структуру с обра­
боткой ошибок
fmt.Println(config.Section.Enabled) О Использование значений из INI-файла
fmt. Println (config. Section. Path)

В данном случае извлечением данных из TNI-файла и сохранением


их в структуре заним ается сторонний пакет gopkg.ir./gcfg.vl О. Э тот
пакет вклю чает средства анализа IN I-файлов и действует подобно
стандартному пакету для работы с форм атом JS O N .
Перед обработкой IN I-файла необходимо создать переменную
для сохранения значений из файла. Так ж е как в случае с ф орм атом
JS O N , структура этой переменной должна соответствовать структуре
IN I-файла. В данном случае структура хотя и похож а на структуру из
примера для ф орм ата JS O N , но содержит вложенную структуру для
раздела ©. R этой структуре будут храниться извлеченные конфигу­
рационные значения.
OOpdOoTKa конфи1Л/ра1_1ИОнной информации 71

Функция ReadFilelnto читает файл в созданную структуру ©. Если


при этом возникает ошибка, выводится соответствующее сообщение.
После этого конфигурация из INI-файла доступна для использова­
ния ©.
Пакет gopkg.in/gcfg.vl содержит несколько функций для чтения
тегов, строк, файлов и прочих элементов, реализующих интерфейс
ic. Reader. Дополнительные сведения о них можно найти в докумен­
тации с описанием пакета.

РЕЦЕПТ 4 Конфигурационные ф айлы или переменные


среды
Конфигурационные файлы, безусловно, представляют собой отлич­
ное транспортное средство для передачи конфигурационных дан­
ных в программы. Но некоторые новые окружения не поддерживают
предположений, которые мы делаем, когда используем традицион­
ные конфигурационные файлы. Иногда приложение не имеет до­
ступа к файловой системе на требуемом уровне. Некоторые системы
полагают, что файлы конфигурации являются частью исходного кода
(и считают их статической частью выполняемого файла). Это огра­
ничивает возможности использования конфигурационных файлов.
Четче всего эта тенденция проявляется в новых системах PaaS об­
лачных служб. Развертывание в этих системах обычно осуществляет­
ся путем помещения пакета исходного кода на управляющий сервер
(подобно сохранению в Git). И единственным способом настройки
приложений на таких серверах является получение данных из пере­
менных окружения. Рассмотрим приемы работы с такими перемен­
ными.
Проблема
Многие системы PaaS не предусматривают доступа к конфигура­
ционным файлам. Возможности настройки в них ограничиваются до­
ступом к таким элементам, как переменные окружения.
Реш ен и е
12-факторные приложения, развертываемые в Heroku, Cloud
Foundry, Deis и других платформах PaaS или системах управления
контейнерами кластеров (рассматриваются в главе 11), становятся
все более обычным явлением. И одним из двенадцати факторов явля-
72 Надежная основа

ется хранение конфигурационных данных в переменных окружения.


Это дает возм ож ность определять отдельные конфигурации для каж ­
дого окружения, в котором выполняется приложение.

12-ф акторные приложения


Это популярная и широко используемая методика создания веб­
приложений и служб. Подобные приложения создаются с учетом сле­
дующих 12 факторов:
1. Использование единой базы кода с контролем версий, которая мо­
жет развертываться многократно.
2. Явное объявление зависимостей и изолированность от других
приложений.
3. Хранение конфигурации приложения в переменных окружения.
4. Подключение поддерживающих служб.
5. Разделение этапов сборки и запуска.
6. Выполнение приложения как одного или нескольких процессов
без состояния.
7. Экспорт служб через ТСР-порт.
8. Горизонтальное масштабирование путем добавления процессов.
9. Повышение живучести приложений за счет быстрого запуска
и штатного завершения.
10. Версии для разработки, тестирования и эксплуатации должны
быть как можно более схожими.
11. Управление журналированием как потоками событий.
12. Запуск административных задач в отдельных процессах.
Более подробные сведения об этих факторах можно найти на страни­
це http://12factor.net.

Рассмотрим, например, переменную окружения PORT, содержащ ую


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

Л истинг 2.12 •> Конфигурация в переменных окружения: er.v_3onfig.go


package ir.ain
inport (
"fmt"
"net/http"
'os
Работа с дейс. i вуюшими веб-серверами ❖ 73

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

fm t.Fprint(res, "The homepage.")

В этом примере используется пакет http из стандартной библио­


теки. Если вы помните, мы использовали его в простом веб-сервере
«Hello World» (листинг 1.16). R следующем разделе мы подробнее
остановимся на веб-серверах.
Процесс извлечения конфигурационных данных из окружения
предельно прост. Функция G etenv из пакета os извлекает значение
в виде строки О. Если переменная окружения не найдена, возвраща­
ется пустая строка. Чтобы преобразовать строку в другой тип, мож­
но воспользоваться пакетом s trc o n v . Например, если бы помер порта
в этом примере должен был быть целым числом, мы могли бы исполь­
зовать функцию P a r s e ln t.
ПРЕДУПРЕЖДЕНИЕ Проявляйте о сторож ность при работе с и н ­
ф ормацией из переменны х окруж ения и с процессам и, полу­
чаю щ ими эту инф ормацию . Н апример, сторонний подчиненны й
процесс, запущ енны й из приложения, также м ожет иметь доступ
к переменны м окружения.

2.3. Работа с действующими


веб-серверами
Стандартная библиотека языка Go дает отличный фундамент для
создания веб-серверов, но, как всегда, кое-что может потребоваться
изменить и кое-что добавить. R данном разделе мы рассмотрим два
важных вопроса: сопоставление путей URL с функциями обратного
вызова и запуск/остановку серверов с особым упором на остановку
в штатном режиме.
Создание веб-серверов является основной задачей пакета http.
Этот пакет используется как основа для обработки T C P -соединений
74 Надежная основа

из пакета n e t. Поскольку веб-серверы являются основной частью


стандартной библиотеки и имеют широкое применение, создание
простого веб-сервера было включено уже в главу 1. Этот раздел вы­
ходит за рамки создания простых веб-серверов и охватывает некото­
рые практические приемы создания приложений. Дополнительные
сведения о пакете ht~p можно найти на странице http://golang.org/
pkg/net/http/.

2.3.1. Запуск и завершение работы сервера


Запуск сервера в языке Go выполняется довольно просто. С помощью
пакета net или http можно создать сервер, подключенный к порту
TCP, и начать отвечать на входящие соединения и запросы. Но что
происходит при выключении сервера? Чем чревато завершение его
работы в тот момент, когда к нему подключены пользователи, или до
того, как были записаны на диск все данные (например, данные поль­
зователей)?
Команды для запуска и остановки сервера в операционной системе
должны выполняться демоном инициализации. Команду go ru n удоб­
но использовать во время разработки, и ее можно задействовать в не­
которых системах, опирающихся на двенадцать факторов, но ято не
стандартный и не рекомендуемый способ. Запуск приложений вруч­
ную прост, по он не предназначен для интеграции с функциональны­
ми инструментами и не может применяться для решения таких проб­
лем, как неожиданная перезагрузка. Для таких ситуаций специально
были созданы демоны инициализации, и они хорошо с ними справ­
ляются.
Большинство систем по умолчанию имеет набор инструментов для
инициализации. Например, systemd ( h ttp s ://fre e d e s k to p .o rg /w ik i/
S o ftw a re /s y s te m d /) часто встречается в дистрибутивах Linux, таких
как Debian и Ubuntu. В сценариях для systemd можно использовать
следующие команды.

Листинг 2.13 ❖ Запуск и остановка приложений с помощью upstart


s systeir.ctl sta r t myapp. service <- Запуск приложения myapp
s systeir.ctl stop myapp. service <- Остановка запущенного приложения myapp

Существует широкий диапазон демонов инициализации. Они раз­


ные в разных операционных системах, и многие из них предназначе­
ны для конкретных версий Linux. Вам могут быть знакомы некоторые
из названий, такие как upstart, in it и launchd. Поскольку разные си­
Работа с дейс. i вуюшими веб-серверами ❖ 75

стемы поддерживают разные команды и конфигурационные сцена­


рии, мы не будем рассматривать их здесь. Эти инструменты хорошо
документированы, и для их изучения существует множество пособий
и примеров.
ПРИМЕЧАНИЕ Мы не рекомендуем писать приложения, выполняю­
щиеся как демоны. Используйте лучше демоны инициализации для
управления запуском/остановкой вашего приложения.

Типичный антиш аблон : обработчик U R L -а д р е с а


Сущ ествует простой шаблон (точнее, антишаблон), часто исполь­
зуемый на этапе разработки, реализующий остановку сервера при пе­
реходе по определенному I JR L -адрес.у, например / k i l l или /shutdown.
Следующий листинг демонстрирует простую версию этого метода.

Л и с т и н г 2 . 1 4 ❖ URL-адрес обратного вызова завершения работы:


callback_shutdcwr.. со
package train

:nport (
"fmt."
"net/http"
"os"
)
func mainO {
http.HandleFuns ("/shutdown", shutdown) Регистрация специального пути
http.HandleFunsi"/", homePaqe) для завершения работы сервера
http.ListeriAndServe(":8080", nil)

func shutdown(res http.ResponseWriter, req *http.Request) {


os.Exit (0) <- Немедленно завершает приложение

func hoireFage(res http.ResponseWriter, req *http.Request) {


i f req.URL.Path != "/" {
http.NotFound(res, req)
return

fmt.Fprir.t(res, "The homepage.")

Мы не рекомендуем этот метод и приводим только потому, что его


легко найти в Интернете. Он прост, что определенно является его пре­
имуществом, но имеет целый ряд недостатков, включая следующие:
7Б Надежная основа

О при передаче в промышленную эксплуатацию U R L -адрес дол­


жен быть заблокирован или, что предпочтительнее, удален. Не­
обходимость иметь разный код для разработки и эксплуатации
чревата появлением ошибок. Если оставить этот U RL-адрес
в коде для промышленной эксплуатации, кто угодно легко смо­
жет отключить службу;
О при переходе по U R L-адресу обработчик запроса немедленно
завершает работу сервера. При этом любые действия будут сра­
зу же немедленно остановлены. Любые несохраненные данные
будут потеряны, поскольку отсутствует возможность их сохра­
нения перед выходом;
О использование URL-адреса происходит в обход обычных функ­
циональных инструментов, таких как Ansible, Chef и Puppet,
или других инструментов инициализации. Следует отдавать
предпочтение более подходящим инструментам, управляю­
щим обновлениями и запущенными приложениями.
Мы не рекомендуем использовать этот метод. Он удобен при раз­
работке, когда процесс выполняется в фоновом режиме или действу­
ет как демон. Go-приложения, как правило, не должны быть демо­
нами, и существуют более совершенные методы запуска и остановки
сервера, даже в процессе разработки.

РЕЦЕПТ 5 Штатное завершение с помощью пакета manners


Перед выключением сервера обычно следует прекратить получение
новых запросов, сохранить данные на диск и нормально завершить
открытые подключения. Пакет http стандартной библиотеки завер­
шает работу немедленно и не дает возможности выполнить ни одно
из этих действий. В некоторых случаях это может привести к потере
или повреждению данных.
Проблема
Чтобы избежать потери данных и непредусмотренного поведения,
серверу может потребоваться выполнить некоторые действия перед
завершением работы.
Реш ен и е
Для решения этой задачи необходимо реализовать собственную
логику или использовать сторонний пакет, например github.com/
braincree/manners.
PdOoTd с действующими веО-сервервми 77

Обсуж дение
В 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() <- О Получение экземпляра обработчика

ch := make (chan os. Signal! © Настройка мониторинга сигналов


signal.N otify (eh, os.Interrupt, o s.K ill) операционной системы
go lister.ForShutdown (ch)
manners.ListenAndServe (":8080", handler) ч- © Запуск веб-сервера

func newHandler() 'handler {


return &handler{)

type handler struct()


func (h *handler) ServeHTTP(res http.ResponseWriter, req *http.Request) (
query := req.URL.Query() Обработчик, О
name := query.Get ("паше") отвечающий
i f name == "" { на веб-запросы
name = "Inigo Montoya"

fm t.Fprint(res, "Hello, my name is ", name)


78 Надежная основа

func listenForShutdown(ch <-chan o s.Sig nal) I © Ожидание сигнала


<-ch завершения работы
manners.Close О

Функция main начинается с получения экземпляра функции


handler, способного реагировать на веб-запросы О. Этот обработчик
генерирует простой ответ Hello World О. Ниже в этой главе на его
место будет помещен более сложный обработчик, поддерживающий
правила маршрутизации, анализирующий пути и обрабатывающий
регулярные выражения.
Для штатного завершения работы нужно знать, когда это можно
сделать. Пакет signal предоставляет средства для получения сигналов
от операционной системы, включая сигналы прерывания или завер­
шения работы приложения. Следующий шаг - настройка канала для
получения сигналов прерывания и завершения от операционной си­
стемы, чтобы получить возможность реагировать на них ©. Функция
ListenAncServe, подобно одноименной функции из пакета http, блоки­
рует выполнение. Для мониторинга сигналов сопрограмма должна
выполняться параллельно. Функция ListenForShutdovm ожидает полу­
чения сигнала через канал © и посылает сообщение Shutdown серверу.
Оно указывает серверу прекратить прием новых подключений и от­
ключиться, как только будут обработаны все текущие запросы.
Вызов функции ListenAndServe запускает сервер, так же как вызов
одноименной функции из пакета http ©.
СОВЕТ Перед заверш ением работы сервер ж дет только завер ш е ­
ния обработки запросов. Если прилож ение запускает со п р о гр а м ­
мы и должно дождаться их заверш ения перед выходом, вам п р и ­
дется реализовать свою версию WaitGroup.

Такой подход имеет несколько преимуществ, в том числе:


О позволяет завершить текущие ПТТР-запросы, не прерывая их
обработки;
О останавливает прием новых запросов, освобождая порт TCP.
Это открывает возможность другому приложению выполнить
привязку к порту и начать обслуживание запросов. При обнов­
лении версии приложения одна может завершить обработку
всех своих запросов, а другая - перейти в оперативный режим
и начать обслуживание.
Однако имеется также пара недостатков, проявляющихся при
определенных условиях:
Работа с действующими веб-серверами 79

О пакет manners работает только с HTTP-подключениями, а не


с любыми TCP-соединениями. Если приложение не является
веб-сервером, пакет manners работать в нем не будет;
О в некоторых случаях одной версии приложения может потре­
боваться перед завершением передать текущие сокеты другому
экземпляру того же или другого приложения. Например, если
между сервером и клиентскими приложениями имеются по­
стоянные подключения, пакет manners может дождаться их за­
вершения или закрыть их принудительно, но он не в состоянии
передавать сокеты.

2.3.2. Маршрутизация веб-запросов


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

РЕЦЕПТ 6 В ы бор пути по содержимому


Веб-приложения и серверы, поддерживающие интерфейс REST,
обычно выполняют разные функции для разных путей. На рис. 2.3 по­
казан фрагмент URL-адреса, представляющий путь. Пример «Hello
World», представленный в листинге 1.16 (глава 1), использует одну
функцию для обработки всех возможных путей. Для простого прило­
жения в стиле «Hello World» этого вполне достаточно. По одна функ­
ция не может нормально обрабатывать несколько путей и не обеспе­
чивает масштабирования в реальных приложениях. В этом разделе
рассматривается несколько методов обработки отличающихся путей,
а в некоторых случаях и отличающихся HTTP-методов (иногда на­
зываемых глаголами).
Проблема
Для маршрутизации запросов веб-сервер должен быстро и эффек­
тивно анализировать фрагмент URL-адреса, представляющий путь.
80 Надежная основа

http://example.com/foo#bar?baz=quo

Фрагмент пути в URL-адресе


Рис. 2.3 ❖ Ф рагмент пути в URL-adpece,
используемой для маршрутизации запросов

Р е ш е н и е : н еско лько о б ра бо тч и к о в
Данное решение расширяет прием из листинга 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"

fm t.Fprint(res, "Hello, my name is ", name)


Работа с действующими веб-серверами ❖ 81

func goodbye(res http.ResponseWriter, req *http.Request) ( <- О Функция-


path := req.URL.Rath © Выборка имени обработчик
parts := string s.Split(p ath, "/") ИЗ строки запроса для пути
name := p a rts[2] /goodbye/
i f name == "" (
name = "Inigo Montoya"

fm t.Fprint(res, "Goodbye ", name)

func horr.ePage(res http.Response^: ite r , req *http.Request) { <- 0 Функция-


i f req.URL.Path != "/" { © Проверка соответствия пути обработчик
http.NotFound(res, req) домашней или неопознанной для домашней
return странице инеопознанной
) страницы
fm t.Fprint(res, "The homepage.")

ПРИМЕЧАНИЕ Данные, полученные от конечного пользователя,


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

Здесь используются три функции для обслуживания трех путей


О. Анализ пути производится в направлении от более конкретных
маршрутов к менее конкретным. Таким наименее конкретным счи­
тается любой путь, которому не будет найдено соответствия до сопо­
ставления с путем /.
Следует отметить, что пути, заканчивающиеся символом /, могут
вызвать проблемы при перенаправлении. Например, при попытке
перейти по пути / g o o d b y e пользователь автоматически будет пере­
направлен на / g o o d b y e / . Параметры в строке запроса при этом могут
быть потеряны. Так адрес / g o o d b y e ? f o o = b a г превратится / g o o d b y e / .
Важно понимать, как действует перенаправление по умолчанию.
Обработчик, зарегистрированный для пути / ' h e l l o , будет исполь­
зоваться исключительно для пусти / h e l l o . Обработчик, зарегистри­
рованный для пути / g o o d b y e / , будет вызываться для путей: / g o o d b y e
(с перенаправлением), / g o o d b y e / , / g o o d b y e / f o o , / g o o d b y e / f o o / Ъ а г и так
далее.
Пути . / h e l l o соответствует функция h e l l o 0 . В качестве аргумен­
тов обработчик получает h t t p . R e s p o n s e W r i t e r и h t t p . R e q u e s t . При не­
обходимости имя для вставки в текст приветствия можно извлечь из
82 Надежная основа

строки запроса по ключу name 0 . Запрошенный U RL хранится в свой­


стве URL аргумента http.R equest. Его метод Query возвращает соответ­
ствующее ключу значение или пустую строку, если указанный ключ
отсутствует в строке запроса. Если имя не указано пользователем, по
умолчанию используется строка Ir.igc Montoya.
СОВЕТ В пакете n e t / u r l , содержащем тип URL, имеется множество
функций для работы с URL-адресами.

ПРИМЕЧАНИЕ Определить использованный HTTP-метод можно


с помощью свойства http.Request.Method. Оно содержит имя метода
(например, GET, POST и так далее).

Функция goodbye обрабатывает путь /'goodbye/', включая случаи при­


сутствия дополнительного текста в конце О. В данном случае путь
может содержать имя для текста приветствия. Например, из пути
/goodbye/Buttercup будет извлечено имя Buttercup 0 . Для этого U R L-
адрес разбивается с помощью пакета strings для выделения фрагмен­
та пути после /goodbye/.
Функция hcmePage обрабатывает корневой путь / и любые другие,
нераспознаваемые пути 0 . Чтобы принять решение о возврате со­
общения «404 Page Not Found» или содержимого домашней страни­
цы, проверяется свойство http.Request.Path 0 . В пакете http имеется
вспомогательная функция Not Found, которую можно использовать
для установки кода НТТР-отвста 404 и отправки текста 404 расе not
found.
СОВЕТ В пакете http имеется функция Error, которую можно и с­
пользовать для установки кода любой ошибки HTTP и возврата
соответствующ его сообщения. Функция NotFounc предназначена
только для возврата ошибки 404.

Использование нескольких функций-обработчиков является ос­


новным способом обслуживания разных путей. Этот способ обладает
следующими преимуществами:
О поскольку этот способ поддерживается пакетом http, он хоро­
шо документирован, проверен, и имеется масса примеров, де­
монстрирующих его использование;
О код отображения путей в функции легко читается и анализи­
руется.
Но, помимо преимуществ, имеются также недостатки, которые за­
ставляют многих разработчиков, включая авторов книги, пользовать­
ся другими методами;
Работа с действующими веб-серверами 83

О нельзя использовать разные функции для разных HTTP-мето­


дов обращения к одному и тому же пути. При создании интер­
фейсов REST для каждого из глаголов (например, GET, POST или
DELETE) может потребоваться реализовать совершенно разную
функциональность;
О подстановки и именованные разделы пути, а также типовые
возможности систем отображения не поддерживаются;
О практически все функции-обработчики должны проверять вы­
ход путей за пределы их компетенции и возвращать сообщение
о том, что страница не найдена. Например, обработчику пути
/goodbye/, представленному в листинге 2.16, будет передан лю­
бой путь, содержащий /goodbye. Все результаты, возвращаемые
в ответ на любые запросы по этому пути, должны производить­
ся только в нем, поэтому если для пути /goodbye/foo/bar/baz
должно возвращаться сообщение о том, что страница не найде­
на, это следует делать только в этом обработчике.
Использование нескольких обработчиков удобно в простых случа­
ях. Такое решение не требует дополнительных пакетов, кроме http,
поэтому внешние зависимости сведены к минимуму. Но если плани­
руется создать более или менее сложное приложение, лучше исполь­
зовать один из методов, описанных ниже.

РЕЦЕПТ 7 Обработка сложных путей с подстановками


Выше был представлен простой метод, но он не способен обраба­
тывать шаблоны путей. При его применении приходится перечис­
лять Rce поддерживаемые пути. Для больших приложений или при­
ложений, реализующих интерфейс REST, необходимо более гибкое
решение.
Пр о б л е м а
Вместо определения конкретных путей для каждого обработчика
приложению может понадобиться более гибкое решение, позволяю­
щее связывать обработчики с простыми шаблонами путей.
Р еш ен и е
В стандартной библиотеке Go имеется пакет path для обработки
путей, где символы слеша используются как разделители. Этот пакет
не имеет прямого отношения к путям URL и прекрасно поддерживает
любые другие пути, но его на удивление удобно использовать в паре
с НТТР-обработчиком.
84 Надежная основа
О бс у ж д ен и е
В следующем листинге демонстрируется маршрутизатор, отобра­
жающий U R L -пути и H T T P -методы в функции-обработчики.

Листинг 2.17 ❖ Анализ URL-адресов с помощью пакета path:


path_handlers.gc
package ir.ain

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-серверу

func newPathResolver() *pathResolver { Создание нового


return ipathResolverfnake(map[siring]http.HandlerFunc)} инициализирован­
ного объекта
pathResolver
type pathResolver struct (
handlers map[string]h ttp .HandlerFunc

func (p *pathResolver) Add(path string, handler http.HandlerFunc) {


p.handlers [path] = handler Добавление путей
для внутреннего поиска
func (р *pathResolver) ServeHTTP(res http.ResponseWnter, req *htto.Request){
check := req.Method + " " + req.URL.Path <- Объединение метода ипути
для проверки
for pattern, handlerFunc := range p.handlers ( <- О Обход зарегистрирован­
ных путей
i f ok, err := path.Match(pattern, check); ok err == n il ( < - ©
handlerFunc (res, reel <- © Вызов функции Проверка соответ-
return для обработки пути ствия текущего пути
} else i f err != n i l { одному из зарегист-
fm t.Fprint(res, err) рированных
}
Работа с действующими веб-серверами 85

http.Not?ound(res, req) <- © Если для пути не нашлось соответствия,


вернуть сообщение о том, что страница не найдена

func h ello (res http.ResponseWriter, req * http.Request) {


query := req.URL.Query()
name := query.Get("name")
i f name == "" {
name = "Tnigo Montoya"

fm t.Fprint(res, "Hello, my name i s ", name)

func goodbye(res http.ResponseWriter, req * http.Request) {


path := req.URL.Path
parts := strin g s.S p lit(p a th , "/")
name := p a r ts [2]
i f name == "" {
name = "Inigo Montoya"

fm t.Fprint(res, "Goodbye ", name)

Функция main начинается совсем иначе - с получения экземпляра


pathResolver О. Он содержит основную логику сопоставления путей.
После создания экземпляра pathResolver в него добавляются два пути
© в виде строк, состоящих из имени H T TP-метода и пути, разделен­
ных пробелом. Вместо имени H TTP-метода или части пути можно
использовать звездочку (*) для обозначения подстановки.
Экземпляр pathResolver передается встроенному НТТР-серверу
в качестве обработчика ©. Чтобы pathResolver мот действовать как
обработчик, необходимо реализовать для пего метод ServeHTTP, опре­
деляемый интерфейсом HandlerFunc, выполняющий анализ путей.
Когда в сервер поступает запрос, метод ServeHTTP просматривает
зарегистрированные пути с помощью pathResolver О. Для каждого за­
проса проверяется H T T P -метод и путь и при обнаружении совпаде­
ния выбирается соответствующая функция ©. Такая проверка необ­
ходима, потому что R E ST -ссрвсрам часто требуются разные функции
для обработки разных H T TP -методов (например, запросов DELETE или
GET). Если совпадение найдено, вызывается обрабатывающая функ­
ция ©. Если нс найдено ни одного подходящего пути, по умолчанию
выводится ошибка 404 Page Not Found 0 .
Обработка путей с помощью пакета path имеет свои плюсы и ми­
нусы. Плюсы:
86 Надежная основа

О простота, обусловленная простотой сопоставления путей;


О входит в состав стандартной библиотеки, и, как следствие, па­
кет pach имеет хорошую документацию и тщательно проверен.
Минусы определяются назначением пакета path для работы с лю­
быми путями, а не только с адресами URL:
О возможности использования подстановок весьма ограничены.
Например, пути foo/* будет соответствовать путь foo/bar, но не
путь foc/bar/baz. При использовании * в качестве знака подста­
новки сопоставление ведется до следующего символа /. Чтобы
добиться соответствия пути foo/6ap/baz, необходимо использо­
вать шаблон foo/’* / *;
О поскольку пакет предназначен для обработки любых путей,
а не только U R L -адресов, в нем отсутствуют некоторые полез­
ные функции. Например, в листинге 2.17 регистрируется путь
/goodbye/*. При переходе по адресу /goodbye в браузере появит­
ся сообщение об отсутствующей странице, тогда как переход
по адресу /goodbye/ будет выполнен без ошибок. Хотя чисто
технически эти пути разные (отличаются наличием завершаю­
щего символа /), это распространенный в Интернете случай,
который обрабатывается неожиданным образом. Такие случаи
требуется выявлять и обрабатывать отдельно.
Этот метод удобно использовать для простых сценариев, и авторам
удавалось с успехом использовать его.

РЕЦЕПТ 8 Сопоставление URL-адресов с шаблонами


Для большинства приложений в стиле R E S T простого сопоставления
с регулярными выражениями более чем достаточно. По как посту­
пить, если требуется пойти дальше и предусмотреть какую-то особен­
ную обработку U R L -адресов? Пакет path для этого не подойдет, по­
скольку поддерживает только простое сопоставление в стиле PO SIX .
Про бл ем а
Простого сопоставления недостаточно для приложения, которому
необходимо обрабатывать пути как текстовые строки, а не как пути
к файлам. Это особенно важно при сопоставлении без учета раздели­
телей путей (/).
Р еш ен и е
Встроенный пакет path поддерживает простые схемы сопоставле­
ния путей, но иногда требуется сложное сопоставление или более
PdOo Iа с дейс i ыуюшими иеО-серверами ❖ 87

полный контроль нал сопоставлением. В этих случаях можно исполь­


зовать регулярные выражения. Объединение встроенной поддержки
регулярных выражений в Go с H T T P -обработчиком позволит соз­
дать быстрое, но в то же время гибкое, средство сопоставления I JR Т.-
путей.
О бс у ж д ен и е
В следующем листинге представлен код, реализующий обход пу­
тей и их анализ, основанный на регулярных выражениях.

Листинг 2.18 ❖ Анализ URL с помощью регулярных выражений:


rsgex_handlers.go
package main

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)

func newPathResolver() *regexResolver {


return SregexResolver(
handlers: make(map[st.ring]hctp.HandlerFunc),
cache: make (map[st.ri ng] *regexp. Regexp),
}

type reqexResolvsr struct (


handlers map[string]http.HandlerFunc
cache map[string] *regexp.Regexp «- Сохранение скомпилированных регулярных
выражений для повторного использования

func (г *regexResolver) Add(regex soring, handler http.HandlerFunc) {


r.handlers[regex] = handler
cache, _ := regexp.Compile(regex)
r.cache[regex] = cache
88 Надежная основа

func (г *regexResolver) ServeHTTP(res http.ResponseWriter, req *http.Request) {


check := req.Method + " " + req.URL.Path © Поиски вызов
for pattern, handlerFunc := range r.handlers •' функции-обработчика
i f r.cache[pattern].M atchstring(check) == true (
handlerFunc(res, req)
return

http. NotFoundf res, req) «- Если соответствие не найдено,


вернуть ошибку Page Not Found

func h ello (res http.ResponseWriter, req * http.Request) (


query := req.'JRL.Query()
name := query.Get("name")
i f name == "" {
name = "Inigo Montoya"

fm t.Fprint(res, "Hello, my name is ", name)

func goodbye(res http.ResponseWriter, req * http.Request) {


path := req.URL.Path
parts := strin g s.S p lit(p a th , "/")
name := ""
i f len(parts) > 2 (
name = p a r ts [2]
;
i f name == "" (
name = "Inigo Montoya"

fm t.Fprint(res, "Goodbye ", name)

Прием сопоставления путей с применением регулярных выраже­


ний (листинг 2.18) напоминает прием из предыдущего примера (лис­
тинг 2.17). Изменились лишь формат регистрации путей и обработ­
чиков и реализация метода ServeHTTP.
Пути регистрируются как регулярные выражения О. Их структу­
ра Iотчем не отличается от структуры при использовании пакета path,
то есть за именем H T T P -метода через пробел следует путь. Шаблон
GET /hello выглядит достаточно просто, гораздо сложнее организован
шаблон (GET|HEAD) /goodbye(/? [A-Za-zO-9]*) ?. Этот более сложный
шаблон соответствует H T TP-методам GET или HEAD. Регулярное вы-
ражениесовнадает с путями /goodbye, /goodbye/ (с символом / в конце)
и /goodbye/, за которым следуют буквы и цифры.
PdOo Id <_действующими ыеО-cepBepdMH 89

В данном случае метод ServeHTTP перебирает регулярные выражения


для поиска совпадения ©. Обнаружив первое совпадение, он вызовет
функцию, зарегистрированную для этого регулярного выражения.
Если путь соответствует нескольким регулярным выражениям, будет
использовано добавленное первым, которое первым и будет проверено.
ПРИМЕЧАНИЕ При добавлении регулярных выражений они ком­
пилируются и кэшируются. Поиск совпадений выполняется с по­
мощью фу| 1кции Match из пакета regexp. Эта функция сначала пытается
скомпилировать регулярное выражение. Поэтому предварительная
компиляция и кэширование позволяют пропустить этап повторной
компиляции при обработке запросов.

Использование регулярных выражений для проверки путей от­


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

РЕЦЕПТ 9 Быстрая маршрутизация (без листинга)


Многие критикуют встроенный пакет http за то, что его механизмы
маршрутизации и мультиплексирования обеспечивают лишь базовые
возможности. В предыдущих разделах было описано несколько прос­
тых приемов работы с пакетом http, но с их помощью не всегда можно
достичь нужной гибкости и производительности или обеспечить все
необходимые возможности встроенного H T T P -сервера. Кроме того,
у вас может появиться желание избежать написания шаблонного
кода маршрутизации.
Про блем а

Встроенный пакет http недостаточно гибок, и его применение


оправдано далеко не всегда.
Реш ение

Выбор функций-обработчиков по U R L -адресам - типичная задача


веб-приложений. Поэтому было создано множество пакетов марш ру­
тизации. Многие просто импортируют существующий марш рутиза­
тор запросов и используют его в приложении.
Ниже приводится список наиболее популярных решений:
О githtb.com /julicnschm iGt/httproutcr - быстрый пакет марш ру­
тизации с акцентом на использование минимального объема
90 Надежная основа

памяти и как можно меньшего времени на обработку марш­


рутов. Поддерживает сопоставление путей без учета регистра
символов, исключение фрагментов / . . / из путей и обрабаты­
вает необязательные завершающие символы /;
О github.com/gonlla/mux - часть комплекта веб-инструментов
Gorilla. Это бесплатная коллекция пакетов компонентов, кото­
рые можно использовать в приложении. Пакет mux реализует
универсальный набор критериев для сопоставления, включая
хост, схемы, H T TP -заголовки и многое другое;
О github.com/bmizcrany/pat - маршрутизатор, действующий по­
добно механизму маршрутизации из библиотеки Sinatra. За­
регистрированные пути удобно просматривать, и они могут
содержать именованные параметры, например /user/:name.
Имеются и другие пакеты с такими же возможностями, такие
как github.com/gorilla/pat.

Б иблиотека для ве б -прилож ений S inatra


Sinatra - это ф реймворк с открытым исходным кодом для разработ­
ки веб-приложений, написанный на языке Ruby. Используется мно­
жеством организаций и послужил прообразом для более чем 50 ана­
логичных ф реймворков на разных языках, в том числе Go.

Каждый пакет имеет разный набор функциональных возможно­


стей и свой программный интерфейс. Кроме того, существует масса
других пакетов маршрутизации. Потратив немного времени па ис­
следования, вы без труда найдете пакет, отвечающий вашим потреб­
ностям.

2.4. И тоги
После реализации фундаментальных элементов можно переходить
к более специфичной части приложения, которая, по сути, и делает
его полезным. В этой главе мы познакомились с несколькими базо­
выми элементами:
О удобная обработка параметров командной строки. Здесь был
охвачен диапазон от легковесных решений до простых фрейм­
ворков для создания консольных приложений и утилит;
О различные способы извлечения конфигурационной информа­
ции в разных форматах из файлов и переменных окружения;
Итоги 91

О запуск и остановка веб-сервера с помощью специализирован­


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