В рамках курса «Golang Developer. Professional» подготовили перевод полезной статьи.

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


Сегодня мы научимся интегрировать приложение Slack в качестве serverless бэкенда.

Вебхуки — популярный пример использования FaaS (функция как услуга) платформ. Их можно использовать для многих различных целей, например, для отправки клиентам уведомлений, содержащих забавные гифки! Используя serverless функцию, довольно удобно инкапсулировать функциональность вебхука, предоставив ее в виде HTTP-эндпоинта. В этом туториале вы узнаете, как интегрировать приложение Slack в качестве serverless (бессерверного) бэкенда с использованием Go и Azure Functions. Вы можете расширять платформу Slack и интегрировать сервисы, реализуя собственные приложения или рабочие процессы, которые будут иметь доступ к полному объему возможностей платформы, что позволяет вам реализовывать впечатляющие возможности в Slack.

Это немного упрощенная версия Giphy для Slack. Оригинальное приложение Giphy для Slack выдает по поисковому запросу целый набор гифок. Для простоты примера приложение-функция, продемонстрированное в этом туториале, возвращает всего одну (случайную) гифку, соответствующую ключевому слову поискового запроса с помощью Giphy Random API. В этой статье содержится пошаговое руководство по развертыванию приложения в Azure Functions и его интеграции с рабочей средой Slack.

В этой статье вы:

  • поймете, что происходит за кулисами с благодаря разбору кода;

  • узнайте, как настроить решение с помощью конфигурации Azure Functions и Slack;

  • и, конечно же, запустите приложение Slack в рабочей среде!

Логика бэкенд-функции написана на Go (код доступен на GitHub). Те, кто работал с Azure Functions возможно уже знают, что Go не является одним из языков, для которых существуют обработчики по умолчанию. Вот где пользовательские обработчики (Custom Handlers) приходят на помощь!

Что такое пользовательские обработчики?

Если вкратце, то пользовательский обработчик - это легковесный веб-сервер, который получает события от узла функций. Единственное, что вам нужно для реализации пользовательского обработчика для вашей любимой среды выполнения/языка, это поддержка HTTP! Это не означает, что пользовательские обработчики ограничены только HTTP-триггерами - вы можете использовать другие триггеры вместе с входными/выходными привязками (input/output bindings) с помощью расширений.

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

Триггер события (через HTTP, хранилище, концентраторы событий и т. д.) вызывает узел функций (Functions host). Пользовательские обработчики отличаются от традиционных функций тем, что узел функций действует как посредник: он выдает запрос полезной нагрузки (payload) на веб-сервер пользовательского обработчика (функции) вместе с полезной нагрузкой, которая содержит триггер, данные входной привязки и другие метаданные для функции. Функция возвращает ответ на узел функций, который передает данные из ответа в выходные привязки функции для дальнейшей обработки.

Обзор

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

Структура приложения

Давайте посмотрим, как настраивается приложение. Так это определено в документации

.
+-- cmd
¦   L-- main.go
+-- funcy
¦   L-- function.json
+-- go.mod
+-- host.json
L-- pkg
    L-- function
        +-- function.go
        +-- giphy.go
        L-- slack.go
  • Файл function.json расположен в папке, имя которой используется в качестве имени функции (по соглашению)

{
    "bindings": [
        {
            "type": "httpTrigger",
            "direction": "in",
            "name": "req",
            "methods": [
                "get",
                "post"
            ]
        },
        {
            "type": "http",
            "direction": "out",
            "name": "res"
        }
    ]
}
  • host.json сообщает хосту функций, куда отправлять запросы, указывая на веб-сервер, способный обрабатывать события HTTP. Обратите внимание на customHandler.description.defaultExecutablePath, который определяет, что go_funcy — это имя исполняемого файла, который будет использоваться для запуска веб-сервера. "enableForwardingHttpRequest": true гарантирует, что необработанные данные HTTP будут отправлены пользовательским обработчикам без каких-либо изменений:

{
    "version": "2.0",
    "extensionBundle": {
        "id": "Microsoft.Azure.Functions.ExtensionBundle",
        "version": "[1.*, 2.0.0)"
    },
    "customHandler": {
        "description": {
            "defaultExecutablePath": "go_funcy"
        },
        "enableForwardingHttpRequest": true
    },
    "logging": {
        "logLevel": {
            "default": "Trace"
        }
    }
}
  • каталоги cmd и pkg содержат исходный код Go. Мы рассмотрим это в следующем подразделе.

Объяснение кода

cmd/main.go настраивает и запускает HTTP-сервер. Обратите внимание, что эндпоинт /api/funcy — это тот, через который узел функции отправляет запрос на HTTP-сервер пользовательского обработчика.

func main() {
 port, exists := os.LookupEnv("FUNCTIONS_CUSTOMHANDLER_PORT")
 if !exists {
   port = "8080"
 }
 http.HandleFunc("/api/funcy", function.Funcy)
 log.Fatal(http.ListenAndServe(":"+port, nil))
}

Вся грязная работа выполняется в function/function.go.

Первая часть — это прочитать тело запроса (из Slack) и убедиться в его целостности с помощью процесса проверки подписи на основе этого набора правил, определенного Slack.

signingSecret := os.Getenv("SLACK_SIGNING_SECRET")
	apiKey := os.Getenv("GIPHY_API_KEY")
	if signingSecret == "" || apiKey == "" {
		http.Error(w, "Failed to process request. Please contact the admin", http.StatusUnauthorized)
		return
	}
	slackTimestamp := r.Header.Get("X-Slack-Request-Timestamp")
	b, err := ioutil.ReadAll(r.Body)
	if err != nil {
		http.Error(w, "Failed to process request", http.StatusBadRequest)
		return
	}
	slackSigningBaseString := "v0:" + slackTimestamp + ":" + string(b)
	slackSignature := r.Header.Get("X-Slack-Signature")
	if !matchSignature(slackSignature, signingSecret, slackSigningBaseString) {
		http.Error(w, "Function was not invoked by Slack", http.StatusForbidden)
		return
	}

После того, как мы убедимся, что функция действительно вызывается через Slack, следующая часть — извлечь поисковый запрос, введенный пользователем (Slack).

vals, err := parse(b)
	if err != nil {
		http.Error(w, "Failed to process request", http.StatusBadRequest)
		return
	}
	giphyTag := vals.Get("text")

Ищем GIF-файлы по поисковому запросу, вызывая GIPHY REST API

giphyResp, err := http.Get("http://api.giphy.com/v1/gifs/random?tag=" + giphyTag + "&api_key=" + apiKey)
	if err != nil {
		http.Error(w, "Failed to process request", http.StatusFailedDependency)
		return
	}
	resp, err := ioutil.ReadAll(giphyResp.Body)
	if err != nil {
		http.Error(w, "Failed to process request", http.StatusInternalServerError)
		return
	}

Анмаршалим ответ, отправленный обратно через GIPHY API, преобразуем его в форму, которую Slack может понять и возвращаем его. Вот и все!

var gr GiphyResponse
json.Unmarshal(resp, &gr)
title := gr.Data.Title
url := gr.Data.Images.Downsized.URL
slackResponse := SlackResponse{Text: slackResponseStaticText, Attachments: []Attachment{{Text: title, ImageURL: url}}}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(slackResponse)
fmt.Println("Sent response to Slack")

Посмотрите функцию matchSignature, если вам интересно узнать больше о процессе проверки подписи, и slack.go и giphy.go (в каталоге function), чтобы увидеть структуры, используемые в Go для представления информации (JSON), которая обменивается между различными компонентами. Они сюда не включены, чтобы сделать эту статью лаконичнее.

Хорошо! Пока что мы разбирали теорию и справочную информацию. Пришло время довести дело до конца! Прежде чем продолжить, убедитесь, что вы позаботились о указанных ниже требованиях.

Необходимые требования

  • Установите Azure functions Core Tools — это позволит вам развернуть функцию с помощью интерфейса командной строки (а также запустить ее для тестирования и отладки локально).

  • Получите ключ API GIPHY — Вам необходимо создать учетную запись GIHPY (это бесплатно!) и создать приложение. Каждое созданное вами приложение будет иметь собственный ключ API.

Сохраните где-нибудь свой ключ API GIPHY, так как вы будете использовать его позже.

В следующих разделах вы узнаете, как развернуть Azure Function и настроить Slack для использования слэш-команд (Slash command).

Настройка Azure Functions

Начните с создания группы ресурсов (Resource Group) для размещения всех компонентов решения.

Создание приложения-функции

Начните с поиска приложения-функции (Function App) на портале Azure и нажмите Добавить (Add).

Введите необходимые сведения: вы должны выбрать Пользовательский обработчик (Custom Handler) в качестве стека среды выполнения (Runtime stack).

В разделе Хостинг (Hosting ) выберите Linux и Consumption (Serverless) для Операционная система (Operating system) и Тип плана (Plan type) соответственно.

Включите Application Insights (при необходимости).

Просмотрите окончательные настройки и нажмите Создать (Create), чтобы продолжить.

После завершения процесса вместе с приложением-функцией будут созданы следующие ресурсы:

Развертывание функции

Клонируйте GitHub репозиторий и создайте (build) функцию

git clone https://github.com/abhirockzz/serverless-go-slack-app
cd serverless-go-slack-app
GOOS=linux go build -o go_funcy cmd/main.go

GOOS=linux используется для создания исполняемого файла Linux, поскольку мы выбрали операционную систему Linux для нашего приложения-функции.

Для развертывания используйте Azure Functions core tools CLI

func azure functionapp publish <enter name of the function app>

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

Настройка Slack

В этом разделе будут рассмотрены шаги, которые необходимо выполнить для настройки приложения Slack (слэш-команды) в рабочей среде:

  • Создайте приложение Slack 

  • Создайте слэш-команду

  • Установите приложение в свою рабочую среду

Создание приложения Slack и слэш-команды

Войдите в Slack и начните с создания нового приложения Slack.

Нажмите Создать новую команду (Create New Command), чтобы определить новую слэш-команду с необходимой информацией. Обратите внимание, что поле URL-адреса запроса (Request URL) - это поле, в которое вы вводите HTTP эндпоинт функции, который представляет собой не что иное, как URL-адрес, который вы получили после развертывания функции в предыдущем разделе. Как только вы закончите, нажмите Сохранить (Save), чтобы закончить.

Установка приложения в свою рабочую среду

После того, как вы закончите создание слэш-команды, перейдите на страницу настроек вашего приложения, нажмите кнопку Основная информация (Basic Information) в меню навигации, выберите Установить ваше приложение в вашей рабочей среде (Install your app to your workspace) и нажмите Установить приложение в рабочей среде (Install App to Workspace) — это установит приложение в рабочую среду Slack, чтобы протестировать ваше приложение и сгенерировать токены, необходимые для взаимодействия с API Slack. Как только вы завершите установку приложения, на той же странице появятся учетные данные приложения (App Credentials).

Запишите куда-нибудь секрет (Signing Secret) вашего приложения, так как вы будете использовать его позже

Прежде чем перейти к самому интересному

… убедитесь, что в конфигурации приложения-функции добавлен SLACKSIGNINGSECRET и GIPHYAPIKEY — они будут доступны как переменные среды внутри функции.

fun(cy) time!

В рабочей среде Slack вызовите команду /funcy <search term>. Например, попробуйте /funcy dog. Вы должны получить обратно случайную гифку!

Краткое напоминание того, что происходит: когда вы вызываете команду /funcy в Slack, она вызывает функцию, которая затем взаимодействует с Giphy API и, наконец, возвращает GIF пользователю (если все идет по плану!).

Вы можете увидеть timeout error из Slack после первого вызова. Скорее всего, это связано с cold start, когда функция загружается несколько секунд, когда вы вызываете ее в первый раз. Это сочетается с тем фактом, что Slack ожидает ответа в течении 3 секунд — отсюда и сообщение об ошибке.

Здесь не о чем беспокоиться. Все, что вам нужно, это повторить попытку, и все должно быть в порядке!

Наводим порядок: как только вы закончите, не забудьте удалить группу ресурсов, которая, в свою очередь, удалит все ресурсы, созданные ранее (приложение-функция, App Service Plan и т. д.).

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


Узнать подробнее о курсе «Golang Developer. Professional».

Записаться на открытый вебинар по теме «Go-каналы снаружи и внутри».