Как сделать сервис на golang
Пару месяцев назад я начал писать на Go так же известным, как Golang. И не только из-за их потрясающего логотипа. Основная причина была в том, что я хотел, чистую железяку на моем Raspberry Pi, а так же кроссплатформенность в паре с легкостью установки. Среди других причин были простота создания на Go асинхронного кода с помощью Go методов и уникальный подход к синхронизации каналов между подпрограммами (потоками) посредствам go routines.
С тех пор, как GO был выпущен в 2012 году, я много читал о нем и что ж, пришло время по-настоящему замарать руки и попробовать использовать его для своих задач.
Однако любопытно, продолжим .
Что же из себя представляет GO?
Go - это опен сорсный язык программирования , который позволяет легко создавать простое, надежное и эффективное программное обеспечение. Разрабатывался он внутри компании Google и на 2018 год уже являлся одним из самых быстроразвивающихся и адаптированных языков программирования. Первый релиз языка появился в марте 2012 года. А не так давно, начиная с выпуска Go 1.11, у него появилась поддержка модулей и управлять зависимостями стало немного удобнее.
Необходимые настройки среды
Для начала нужно все правильно настроить. Есть простой способ установить мою библиотеку с помощью Chocolatey.
В Windows мы используем Chocolatey в Powershell для установки Go и vscode…
С установленным Go и vscode у нас есть все, что нужно для начала.
Так как я использую Git bash на все случаи жизни, я запускаю следующую команду Visual Studio Code из Git Bash, чтобы установить go расширение. Если хотите, вы можете просто установить расширение из самого vscode.
Вот мы и поставили Go расширение в visualstudio, так что теперь у нас есть все для приятного кодинга.
Где найти отличные ресурсы и материалы по GO?
Документацию по Go можно найти здесь. Все пакеты, доступные для использования, можно найти здесь.Кроме того, для того, чтобы заюзать Go или помочь другим программистам с примерами у языка есть онлайн-площадка.
Создаем первый проект
Для того, чтобы начать на Go новый проект, мы сначала создаем пустую папку. Затем мы прописываем в этой папке команду go modules init, чтобы инициализировать модули Go. Модули используются для зависимостей.
Синтаксис
В Go мы определяем функцию, используя ключевое слово func.
То, что других языках обычно бывает классом, в Go является структурой struct. Вы можете определить структуру следующим образом.
Все переменные, функции, структуры, которые начинаются с верхнего регистра в Go являются публичными. А все переменные, функции, структуры, начинающиеся со строчной буквы, являются частными. Кроме того, как вы наверное уже заметили в примере, определения типов идут за именем переменной. Поместив оператор импорта поверх файла вы можете импортировать все необходимые пакеты. И последнее, но не менее важное: каждая программа go должна иметь хотя бы один main пакет.
Этот пакет fmt предоставляет нам несколько полезных функций, например строки шаблонов, которые удобно использовать для построения строки с использованием некоторых переменных.. Более того, в примере мы видим, что возвращаемый тип для функции упоминается в конце ее определения. Более подробно читайте о пакете fmt в документации.
Теперь у нас есть пустой проект, и к тому же мы уже немного знаем о синтаксисе, чтож, начнем! Открываем эту папку в vscode.
Сначала мы добавляем новый файл в нашу папку, называем ее main.go . Затем добавим тот код, который мы рассмотрели выше.
Теперь с помощью этого кода мы можем запустить наше приложение. Используя следующую команду в командной строке.
Можете также прогнать этот код в Go playground.
Тестирование
Чтобы добавить тесты в go, просто добавьте файл main_test.go. Добавим функцию проверяющую метод sayHi. Сигнатура методов тестирования всегда ДОЛЖНА быть TestXxx(t *testing.T). Первый X всегда должен быть в верхнем регистре.
Теперь, чтобы запустить наш тест, мы сделаем в командной строке следующее. Обратите внимание, что я запускаю все тесты из всех файлов и подпапок в моем проекте таким способом ./.
Пожалуйста, не бойтесь пробовать, изменяйте реализацию, и снова запустите тест, чтобы увидеть, как тесты пройдут неудачно. Для запуска с code coverage запустите такие тесты.
Чтобы подробнее ознакомится, пожалуйста, читайте документацию о testing package.
Компилируем и выполняем
Самым простым способом создать исполняемый файл будет:
Это команда создаст нам исполняемый файл Windows, как будто мы на Windows. Вы можете просто вызвать исполняемый файл и увидеть тот же результат, что мы имели при вызове go run main.go. Если мы хотим получить более подробный вывод и посмотреть, что происходит за под капотом, можно использовать -x опцию -в go build. А как нам скомпилировать один и тот же код для Linux или Raspberry Pi?
Чтобы запустить этот бинарник в Linux, не забудьте использовать chmod +x, а затем просто вызвать с помощью ./hello-world
Raspberry Pi 3 имеет архитектуру ARM v7, которую можно скомпилировать следующим образом. ПРИМЕЧАНИЕ: все это мы можем сделать с Windows.
Для запуска бинарника сделайте все то же самое, что и на любом другом компьютере с Linux chmod +x, а затем ./hello-world для запуска вашего приложения.
Резюмируя
Я знаю, что это очень простое приложение и оно не несет практической пользы, но, по крайней мере, вы уже, как минимум, настроили свою среду разработки и приобрели небольшой практический опыт в написании первых строк кода на Go. Вы уже также знаете, как писать тесты. Это будет хорошей основой для написания качественного кода. Более интересные вещи, такие как go routines, я хотел бы оставить для нового поста в блоге.
И последнее, но не менее важное: я хотел бы поделиться с вами своим первым приложением на Go. Это интеграция с термостатом Nest, который я запускаю на своем Raspberry Pi, чтобы собрать данные о температуре и отобразить их в хорошем веб-приложении на React. Только не забудьте зарегистрироваться в рабочем пространстве Gophers Slack.
Это руководство представляет собой базовое введение в работу с gRPC в Golang.
Изучив этот пример, вы:
- Определите службу в файле .proto.
- Сгенерируйте серверный и клиентский код с помощью protocol buffer компилятора.
- Используйте Go gRPC API, чтобы написать простой клиент и сервер для вашей службы.
Обратите внимание, что в примере в этом руководстве используется версия proto3 языка protocol buffers.
Зачем использовать gRPC?
Наш пример представляет собой простое приложение для сопоставления маршрутов, которое позволяет клиентам получать информацию об объектах на своем маршруте, создавать сводку своего маршрута и обмениваться информацией о маршруте, такой как обновления трафика, с сервером и другими клиентами.
С помощью gRPC мы можем один раз определить нашу службу в файле .proto и создать клиентов и серверы на любом из поддерживаемых языков gRPC, которые, в свою очередь, могут быть запущены в различных средах, от серверов в большом центре обработки данных до вашего собственного планшета - вся сложность общение между разными языками и средами осуществляется за вас через gRPC. Мы также получаем все преимущества работы с protocol buffers, включая эффективную сериализацию, простой IDL и легкое обновление интерфейса.
Настройка
У вас уже должны быть установлены инструменты, необходимые для генерации кода клиентского и серверного интерфейса.
Получите пример кода
Код примера является частью репозитория grpc-go.
Загрузите репозиторий в виде zip-файла и разархивируйте его или клонируйте репозиторий:
Перейдите в каталог с примером:
Определение сервисы
Чтобы определить службу, вы указываете именованную службу в вашем файле .proto:
Затем вы определяете методы rpc внутри определения службы, указывая их типы запроса и ответа. gRPC позволяет определить четыре вида методов службы, все из которых используются в службе RouteGuide:
1. Простой RPC, при котором клиент отправляет запрос на сервер с помощью заглушки и ждет ответа, как при обычном вызове функции.
Генерация клиентского и серверного кода
Затем нам нужно сгенерировать клиентский и серверный интерфейсы gRPC из нашего определения службы .proto. Мы делаем это с помощью protocol buffer компилятора protoc со специальным плагином gRPC Go.
В каталоге examples/route_guide выполните следующую команду:
Выполнение этой команды создает следующие файлы в каталоге routeguide:
Создание сервера
Сначала давайте посмотрим, как мы создаем сервер RouteGuide. Если вас интересует только создание клиентов gRPC, вы можете пропустить этот раздел и сразу перейти к созданию клиента (хотя вам все равно это может показаться интересным!).
Чтобы наша служба RouteGuide выполняла свою работу, нужно сделать две части:
- Реализация интерфейса службы, созданного на основе нашего определения службы: выполнение фактической "работы" нашей службы.
- Запуск сервера gRPC для прослушивания запросов от клиентов и их отправки в нужную реализацию службы.
Вы можете найти пример сервера RouteGuide в server/server.go. Давайте подробнее рассмотрим, как он работает.
Реализация RouteGuide
Как видите, у нашего сервера есть тип структуры routeGuideServer, который реализует сгенерированный интерфейс RouteGuideServer:
Простой RPC
RouteGuideServer реализует все наши методы сервиса. Давайте сначала рассмотрим простейший тип, GetFeature, который просто получает Point от клиента и возвращает информацию о соответствующей функции из своей базы данных в Feature.
В метод передается объект контекста для RPC и клиентский protocol buffer запрос Point. Он возвращает объект protocol buffer Feature с ответной информацией и ошибкой. В этом методе мы заполняем Feature соответствующей информацией, а затем возвращаем ее вместе с нулевой ошибкой, чтобы сообщить gRPC, что мы закончили работу с RPC и что Feature может быть возвращен клиенту.
RPC потоковой передачи на стороне сервера
В этом методе мы заполняем столько объектов Feature, сколько нам нужно вернуть, записывая их в RouteGuide_ListFeaturesServer, используя его метод Send(). Наконец, как и в нашем простом RPC, мы возвращаем нулевую ошибку, чтобы сообщить gRPC, что мы закончили писать ответы. Если в этом вызове произойдет какая-либо ошибка, мы вернем ошибку, отличную от нуля; уровень gRPC преобразует его в соответствующий статус RPC для отправки по сети.
Клиентская потоковая передача RPC
Двунаправленный потоковый RPC
Наконец, давайте посмотрим на наш двунаправленный потоковый RPC RouteChat().
Запуск сервера
После того, как мы реализовали все наши методы, нам также необходимо запустить сервер gRPC, чтобы клиенты действительно могли использовать наш сервис. В следующем фрагменте показано, как мы это делаем для нашей службы RouteGuide:
Чтобы собрать и запустить сервер:
1. Укажите порт, который мы хотим использовать для прослушивания клиентских запросов, используя:
2. Создайте экземпляр сервера gRPC с помощью grpc.NewServer(. ).
3. Зарегистрируйте нашу реализацию сервиса на сервере gRPC.
4. Вызовите Serve() на сервере с данными нашего порта, чтобы выполнить блокирующее ожидание, пока процесс не будет убит или не будет вызван Stop().
Создание клиента
В этом разделе мы рассмотрим создание клиента Go для нашей службы RouteGuide. Вы можете увидеть полный пример клиентского кода в examples/route_guide/client/client.go.
Создание заглушки
Чтобы вызвать методы сервиса, нам сначала нужно создать канал gRPC для связи с сервером. Мы создаем его, передавая адрес сервера и номер порта в grpc.Dial() следующим образом:
Вы можете использовать DialOptions для установки учетных данных аутентификации (например, учетных данных TLS, GCE или JWT) в grpc.Dial, когда они требуются службе. Сервис RouteGuide не требует никаких учетных данных.
После настройки канала gRPC нам понадобится клиентская заглушка для выполнения RPC. Мы получаем ее с помощью метода NewRouteGuideClient, предоставляемого пакетом pb, сгенерированным из примера файла .proto.
Вызов сервисных методов
Теперь давайте посмотрим, как мы вызываем наши методы обслуживания. Обратите внимание, что в gRPC-Go RPC работают в блокирующем/синхронном режиме, что означает, что вызов RPC ожидает ответа сервера и либо возвращает ответ, либо ошибку.
Простой RPC
Вызов простого RPC GetFeature почти так же прост, как вызов локального метода.
Как видите, мы вызываем метод полученной ранее заглушки. В параметрах нашего метода мы создаем и заполняем объект protocol buffer запроса (в нашем случае Point). Мы также передаем объект context.Context, который позволяет нам при необходимости изменять поведение нашего RPC, например таймаут/отмену RPC в полете. Если вызов не возвращает ошибку, мы можем прочитать ответную информацию с сервера по первому возвращаемому значению.
RPC потоковой передачи на стороне сервера
Здесь мы вызываем серверный метод потоковой передачи ListFeatures, который возвращает поток географических объектов. Если вы уже прочитали "Создание сервера", некоторые из них могут показаться вам очень знакомыми - потоковые RPC реализованы одинаковым образом с обеих сторон.
Клиентская потоковая передача RPC
RouteGuide_RecordRouteClient имеет метод Send(), который мы можем использовать для отправки запросов на сервер. После того, как мы закончили писать запросы нашего клиента в поток с помощью Send(), нам нужно вызвать CloseAndRecv() в потоке, чтобы сообщить gRPC, что мы закончили запись и ожидаем ответа. Мы получаем наш статус RPC из ошибки, возвращенной CloseAndRecv(). Если статус равен nil, то первым возвращаемым значением от CloseAndRecv() будет действительный ответ сервера.
Двунаправленный потоковый RPC
Создадим микросервис, поместим его в докер, проведем его масштабирование на нескольких виртуальных машинах с помощью оркестрации Docker Swarm, выполним также CD\CD микросервиса с помощью GitHub Action (Микросервис взят с прода, обрезан лишний функционал) будет показан пример его взаимодействия с 1С клиентом.
Предыдущие и будущие статьи:
Для понимания материала возьмем небольшой функционал, реализуемый микросервисом. Есть некий справочник, в котором аналитики могут просматривать количество записей в выбранной таблице postgresql, очищать эту таблицу, а также добавлять колонки, изменять их, добавлять индексы (Сделано это чтобы аналитики не беспокоили ИТ-шников по всяким пустякам).
Возьмем для примера только функционал получения количества записей и очистки, выглядит это в 1С так.
Вырезал из микросервиса все лишнее и положил его в ранее опубликованный GitHub репозиторий посмотреть можно
А сам микросервис запушен в 80 контейнерах, которые система распределяет по серверам (можно сказать, оркестрирует).
Развернуть все это можно yml скриптом, он очень похож на docker-compose файл.
Стрелочками я выделил количество контейнеров, а также образ для контейнера. Собрал я образ с помощью GitHub Action (инструмент из области CD\CI).
Для сборки образа нужен докер файл, ранее я описывал как его делать.
Вкратце - GitHub Action на основании скрипта собирает образ и помещает его в реджестри (регистр образов для докера). Кстати, Docker Hub является одним из самых популярных публичных регистров образов. Как туда поместить свой собственный образ я тоже писал ранее.
GitHub Action выглядит примерно так:
Видел, как некоторые делали CD\CI 1С на GitHub Action, найти такие материалы - это возможность погрузиться в этот функционал глубже.
Когда выполняется коммит в код микросервиса, стартует сборка, которая в конечном итоге обновляет докер образ в registry, а Docker в режиме Swarm обновляет запущенные контейнеры на новый образ перезапуская все 80 запущенных реплик.
Я сделал докер образ публичным, можете с ним экспериментировать, если интересно
В процессе очередной статьи из серии статей по фрэймворку Bettercap 2.* и атаки на WiFi сети, написал простенький код на языке Golang(GO) для выполнения команд интерпретатора удалённо. И решил не вставлять этот код в тело статьи, а вынести в отдельный топик со ссылками на созданный репозито́рий, а заодно не много расписать ход составления алгоритма для выполнения подобной задачи и сам код подробней.
Честно признаюсь думал только воткну код в тело статьи и забуду, но недавно созданный топик с вопросом мистера @.Method, а так же ответ мистера @digw33d, подтолкнули на создания некой инструкции для решения подобных вопросов.
Речь пойдёт о примитивном решении вопроса из серии "Как . ". Конкретный вопрос в нашем случае - "Как создать программу которая будет выполнять команды встроенного интерпретатора ОС удалённо ."
Написать программу которая будет скрыто выполнять команды встроенного интерпретатора ОС, а так же будет получать вывод интерпретатора о выполненной команде и отсылать ответ.
Простейшим решением будет создать программу в стиле "Клиент - Сервер".
"Серверная" часть будет скрыто выполнять команды и отсылать "клиентской" части программы.
Первое что потребуется для решения задачи, это придумать алгоритм для программы, после конечно же подобрать функции которые потребуются для выполнения этой задачи и наконец собрать подобранные функции в программу.
Серверная часть программы
Интересует только "exec.Command" - "Команда exec.Command является объектом, который предоставляет доступ к этому внешнему процессу."
Этот объект и будем использоваться в коде серверной части программы.Рассмотрим пример использования объекта "exec.Command" на примере с встроенным интерпретатором ОС Windows.(На момент написания статьи, работаю в ОС Windows и "удобней" описывать использование в этой "оперативной системе". Но так как Golang кроссплатформенный язык программирования, то работать будет и в других ОС, отличие будет только в командах к самому интерпретатору ОС.)
Для примера обратимся к командной строке с командой "help", которая выведет справку командной строки(play golang):
Разница только в одной строке, а точнее только в опции и аргументах к одному объекту "exec.Command". Код при выполнении обратится к встроенному интерпретатору с командой "help", результат будет вывод интерпретатора:
Да вывод печальный и радует глаз непонятными знаками в вперемешку с какими то иероглифами и названиями команд на англ. языке.Вывод других команд будет в таком же формате, виной такому выводу - кодировка.
Решается пакетом "golang.org/x/text/encoding/charmap", для использования потребуется установка:
Используется из пакета команда "NewDecoder()", с указанием нужного типа charmap, в конкретном случае это "CodePage866".
Полное описание пакета и функций, а так же полный перечень типов здесь.
Используем для декодирования ответа командной строки(play golang):
Результат декодирования с подобранной кодировкой :
Теперь добавим пакет Syscal с указанием типа SysProcAttr для того что бы при выполнение команд командная строка не всплывала и не выявляла выполнения команд встроенного интерпретатора и завернём в отдельную функцию которая будет использоваться серверной частью:
Для того что бы не отображалось окно консоли при запуске скомпилированного исходного кода, компилируем с ключами:
На этом этапе функция которая будет отдавать встроенному интерпретатору windows готова. Остаётся написать часть которая будет отвечать за общение между серверной и клиентской частью. Такие программы на языке Go пишутся в два шага, не надо писать тонны кода, всё уже написано, нужно просто взять и использовать.
Будем использовать пакет net, конкретно для серверной части программы понадобится только три команды из этого пакета.Это команда Listen, Accept и Write.
Согласно этому списку портов, порт 8081 официально не используется ни каким приложением или программой, сервер устанавливает прослушивание на порт 8081 и открывает.
Часть отвечающая за соединение и общение между серверно-клиентской программой готова:
Теперь соединим часть отвечающею за общение с функцией которая будет отдавать команды встроенному интерпретатору.
Для этого добавим функцию которая будет обрабатывать полученную строку (удалять переносы строки) и соединим в одно приложение:
Серверная часть полностью готова к работе!
Для того что бы скомпилированная серверная часть не открывала окно консоли при запуски, необходимо компилировать с следующими ключами:
Клиентская часть программы
С клиентской частью на много проще, а в случае с языком Go, этот кода будет содержать минимум строк.
Будут использоваться те же пакеты что и в серверной части, за исключением пакета "os/exec", тут будет использоваться пакет "os", а так же не будут использоваться пакеты "syscall" и "golang.org/x/text/encoding/charmap".
Для подключения к серверной части используется функция Dial, которая подключается на указанный порт указанного ip адреса.
Исходник расписан построчно, алгоритм работы клиента описан выше, даже не приходит в голову что ещё можно описать о клиентской части, разве что на 13 строке (conn, _ := net.Dial("tcp", "127.0.0.1:8081")) в случае использования на удалённой машине нужно указать необходимый ip адрес, заменив "127.0.0.1" на необходимый.
Написать программу для решения конкретной задачи, проще когда есть конкретная задача. Конечно знания языка программирования необходимы, но эти знания только облегчают решение поставленной задачи.В большинстве случаев достаточно поверхностных знаний, понимания синтаксиса используемого языка и желания решить эту задачу. А для остального есть сеть интернет, там есть дядька Google, а так же множество форумов и целое мировое сообщество в котором найдутся "добрые люди" которые дадут необходимую подсказку для решения поставленной задачи.
Ссылка на репозиторий с исходным кодом программы здесь, исходный код код и созданные в ходе написания статьи бинарники здесь.
Читайте также: