Webhook 后端是 FaaS(功能即服务)平台的流行用例。它们可用于许多用例,例如发送客户通知以使用有趣的 GIF 进行响应!使用 Serverless 功能,封装 webhook 功能并以 HTTP 端点的形式公开它非常方便。在本文章中,你将学习如何使用Azure Functions和Go将Slack 应用实现为无服务器后端。您可以通过实施自定义应用程序或工作流来扩展 Slack 平台并集成服务,这些应用程序或工作流可以访问平台的全部范围,从而允许您在 Slack 中构建强大的体验。
这是Slack的Giphy的更简单版本。最初的 Giphy Slack 应用程序通过响应多个 GIF 来响应搜索请求。为简单起见,本文中演示的函数应用程序仅使用Giphy Random API返回与搜索关键字对应的单个(随机)图像。这篇博文提供了将应用程序部署到Azure Functions并将其与 Slack 工作区集成的分步指南。
在这篇文章中,您将:
- 了解Azure Functions 中的自定义处理程序
- 通过简短的代码演练了解幕后发生的事情
- 了解如何使用配置 Azure Functions 和 Slack 设置解决方案
- 当然,在工作区中运行您的 Slack 应用程序!
后端函数逻辑是用 Go 编写的(代码可在 GitHub 上找到。使用过 Azure Functions 的人可能还记得 Go不是默认支持的语言处理程序之一。这就是自定义处理程序来救援的地方!
什么是自定义处理程序?
简而言之,自定义处理程序是一个轻量级的 Web 服务器,它接收来自 Functions 主机的事件。在您最喜欢的运行时/语言中实现自定义处理程序唯一需要的是:HTTP 支持!这并不意味着自定义处理程序仅限于HTTP 触发器- 您可以通过扩展包自由使用其他触发器以及输入和输出绑定。
这是自定义处理程序如何在高层工作的摘要(下图摘自文档)
事件触发器(通过 HTTP、存储、事件中心等)调用 Functions 主机。该办法自定义处理程序从传统的功能不同的是,该功能的主机充当中间人:它有一个沿发出请求负载到自定义处理程序(功能)的Web服务器负载包含触发器,输入数据绑定,等函数的元数据。该函数将响应返回给 Functions 主机,该主机将数据从响应传递到函数的输出绑定以进行处理。
概述
在我们深入研究其他领域之前,通过探索代码(顺便说一下相对简单)可能有助于理解本质
应用结构
让我们看看应用程序是如何设置的。这是在文档中定义的
java:
├── cmd
│ └── main.go
├── funcy
│ └── function.json
├── go.mod
├── host.json
└── pkg
└── function
├── function.go
├── giphy.go
└── slack.go
该function.json文件位于一个文件夹中,其名称按照惯例用作函数名称。
java:
{
"bindings": [
{
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": [
"get",
"post"
]
},
{
"type": "http",
"direction": "out",
"name": "res"
}
]
}
host.json通过指向能够处理 HTTP 事件的 Web 服务器,告诉 Functions 主机将请求发送到何处。注意 customHandler.description.defaultExecutablePath,它定义了go_funcy将用于运行网络服务器的可执行文件的名称。"enableForwardingHttpRequest": true确保将原始 HTTP 数据发送到自定义处理程序而无需任何修改。
java:
{
"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目录包含围棋源代码。让我们在下一个小节中探讨这一点。
代码演练
cmd/main.go设置并启动 HTTP 服务器。请注意,/api/funcy端点是 Function 主机向自定义处理程序 HTTP 服务器发送请求的端点。
java:
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 定义的配方的签名验证过程确保其完整性。
java:
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) 用户输入的搜索词。
java:
vals, err := parse(b)
if err != nil {
http.Error(w, "Failed to process request", http.StatusBadRequest)
return;
}
giphyTag := vals.Get("text")
通过调用 GIPHY REST API 使用搜索词查找 GIF。
java:
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 可以理解的形式,然后返回。就是这样!
java:
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)。为了保持这篇文章的简洁性,这里没有包含这些内容。
好的,到目前为止,我们已经介绍了很多理论和背景信息。是时候把事情做好了!在继续之前,请确保您满足以下提到的先决条件。
先决条件
- 如果您还没有Go,请下载并安装它。
- 安装Azure Functions Core Tools ̶ 这将允许您使用 CLI 部署该函数(并且还可以在本地运行它进行测试和调试)。
- 如果没有,请创建一个 Slack 工作区。
- 获取 GIPHY API 密钥 - 您需要创建一个 GIHPY 帐户(它是免费的!)并创建一个应用程序。您创建的每个应用程序都有自己的 API 密钥。
请复制您的 GIPHY API 密钥,因为您稍后将使用它。
接下来的部分将指导您完成部署 Azure 函数和为 Slash 命令配置 Slack 的过程。
Azure 函数设置
首先创建一个资源组来托管解决方案的所有组件。
创建函数应用
- 首先在 Azure 门户中搜索Function App,然后单击添加。
- 输入所需的详细信息:您应该选择自定义处理程序作为运行时堆栈。
- 在Hosting部分,分别为操作系统和计划类型选择Linux和Consumption (Serverless)。
- 启用 Application Insights(如果需要)。
- 查看最终设置并单击创建以继续。
- 该过程完成后,还将与 Function App 一起创建以下资源:
- 应用服务计划(在这种情况下是消费/无服务器计划)。
- 一个Azure存储账户。
- 一个Azure应用程序启示作用。
部署函数
克隆 GitHub 存储库并构建函数。
java:
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为 Function App选择了操作系统。
若要部署,请使用 Azure Functions 核心工具 CLI。
java:
func azure functionapp publish <enter name of the function app>
部署后,复制命令 ̶ 返回的函数 URL,您将在后续步骤中使用它。
配置松弛
本节将介绍在工作区中设置 Slack 应用程序(Slash 命令)所需执行的步骤:
- 创建一个 Slack 应用程序。
- 创建斜线命令。
- 将应用程序安装到您的工作区。
创建 Slack 应用程序和 Slash 命令
登录到您的Slack 工作区并开始创建一个新的 Slack 应用程序。
单击“创建新命令”以使用所需信息定义新的斜线命令。请注意,请求 URL字段是您将输入函数的 HTTP 端点的字段,它只是您在上一节中部署函数后获得的 URL。完成后,点击保存完成。
将应用程序安装到您的工作区
完成 Slash 命令的创建后,前往应用程序的设置页面,单击导航菜单中的基本信息功能,选择将应用程序安装到工作区, 然后单击将应用程序安装到工作区̶ 这会将应用程序安装到您的工作区 用于测试您的应用程序并生成与 Slack API 交互所需的令牌的 Slack 工作区。应用程序安装完成后,应用程序凭据将显示在同一页面上。
记下您的应用签名密钥,因为您稍后将使用它。
在你进入有趣的部分之前
确保更新 Function App 配置以添加 Slack Signing Secret ( SLACK_SIGNING_SECRET) 和 Giphy API 密钥 ( GIPHY_API_KEY) ̶ 它们将作为函数内的环境变量可用。
FUN(cy)时间!
我们可以从你的 Slack 工作区,调用命令/funcy <search term>. 例如尝试/funcy dog。
你应该拿回一个随机的 GIF 作为回报!
简单回顾一下正在发生的事情:当您/funcy在 Slack 中调用命令时,它会调用该函数,然后与 Giphy API 交互并最终将 GIF 返回给用户(如果一切顺利)。
您可能会timeout error在第一次调用后从 Slack 中看到。这很可能是因为cold start该函数在第一次调用时需要几秒钟的时间来引导。这与Slack 期望在 3 秒内得到响应的事实相结合,给出了错误消息。
没有什么可担心的。您所需要的只是重试,一切都会好起来的!
清理
当我们在完成后这个测试之后不要忘记删除资源组,就是将删除之前创建的所有资源(功能应用、应用服务计划等)。
结论
没有什么能阻止您在 Azure 上使用 Go 实现无服务器功能!我希望这是一种尝试自定义处理程序的有趣方式。