“效率”的代码演练-使用Azure Functions的无服务器Slack应用

In the previous blog, we explored how to deploy a serverless backend for a Slack slash command on to Azure Functions. As promised, it's time to walk through the code to see how the function was implemented. The code is available on GitHub for you to grok.

If you are interested in learning Serverless development with Azure Functions, simply create a free Azure account and get started! I would highly recommend checking out the quickstart guides, tutorials and code samples in the documentation, make use of the guided learning path in case that's your style or download the Serverless Computing Cookbook.

Structure

代码结构如下:

  • 功能逻辑位于有趣的类(包com.abhirockzz.funcy)并且,其余的内容包括模型类(又名POJO)-包com.abhirockzz.funcy.model.giphy和com.abhirockzz.funcy.model.slack for GIPHY和Slack respectively

Function entry point

的handleSlackSlashCommand方法定义函数的入口点。 它装饰有@FunctionName注释(并定义函数名称-有趣的)

@FunctionName(funcy)
public HttpResponseMessage handleSlackSlashCommand(
        @HttpTrigger(name = "req", methods = {HttpMethod.POST}, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<String>> request,
        final ExecutionContext context)

它是通过HTTP请求(来自Slack)触发的。 这是由@HttpTrigger注解。

@HttpTrigger(name = "req", methods = {HttpMethod.POST}, authLevel = AuthorizationLevel.ANONYMOUS)

注意该函数:

  • 响应HTTP开机自检不需要显式授权进行调用

For details, check out the Functions Runtime Java API documentation

Method parameters

的handleSlackSlashCommand由两个参数组成-HttpRequestMessage和执行上下文。

HttpRequestMessage是Azure Functions Java库中的帮助程序类。 它的实例在运行时注入,用于访问HTTP消息正文和标头(由Slack发送)。

执行上下文提供了到函数运行时的钩子-在这种情况下,它用于获取java。util。logging。Logger。

For details, check out the Functions Runtime Java API documentation

From Slack Slash Command to a GIPHY GIF - core logic

从最终用户的角度来看,旅途是短暂而甜蜜的! 但是,这是幕后情况的要点。

Slack请求的类型应用程序/ x-www-form-urlencoded,然后先进行解码,然后将其转换为Java地图为了易于使用。

    Map<String, String> slackdataMap = new HashMap<>();
    try {
        decodedSlackData = URLDecoder.decode(slackData, "UTF-8");
    } catch (Exception ex) {
        LOGGER.severe("Unable to decode data sent by Slack - " + ex.getMessage());
        return errorResponse;
    }

    for (String kv : decodedSlackData.split("&")) {
        try {
            slackdataMap.put(kv.split("=")[0], kv.split("=")[1]);
        } catch (Exception e) {
            /*
            probably because some value in blank - most likely 'text' (if user does not send keyword with slash command).
            skip that and continue processing other attrbiutes in slack data
             */
        }
    }

see https://api.slack.com/slash-commands#app_command_handling

GIPHY API密钥和Slack Signing Secret是该功能正常运行所必需的。 这些是通过环境变量预期的-如果它们不存在,我们将很快失败。

    String signingSecret = System.getenv("SLACK_SIGNING_SECRET");

    if (signingSecret == null) {
        LOGGER.severe("SLACK_SIGNING_SECRET environment variable has not been configured");
        return errorResponse;
    }
    String apiKey = System.getenv("GIPHY_API_KEY");

    if (apiKey == null) {
        LOGGER.severe("GIPHY_API_KEY environment variable has not been configured");
        return errorResponse;
    }
Signature validation

发送给我们函数的每个Slack HTTP请求都在X-Slack签名HTTP标头。 它是通过使用标准将请求的正文和Slack应用程序签名秘密结合在一起创建的HMACSHA256键控哈希。

The function also calculates a signature (based on the recipe) and confirms that it the same as the one sent by Slack - else we do not proceed. This is done in the matchSignature method

private static boolean matchSignature(String signingSecret, String slackSigningBaseString, String slackSignature) {
    boolean result;

    try {
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(new SecretKeySpec(signingSecret.getBytes(), "HmacSHA256"));
        byte[] hash = mac.doFinal(slackSigningBaseString.getBytes());
        String hexSignature = DatatypeConverter.printHexBinary(hash);
        result = ("v0=" + hexSignature.toLowerCase()).equals(slackSignature);
    } catch (Exception e) {
        LOGGER.severe("Signature matching issue " + e.getMessage());
        result = false;
    }
    return result;
}

一旦签名匹配,我们就可以确信Slack确实调用了我们的函数。

Invoking the GIPHY API

The GIPHY Random API is invoked with the search criteria (keyword) sent by user (along with the Slash command) e.g. /funcy cat - cat is the keyword. This taken care of by the getRandomGiphyImage and is a simple HTTP GET using the Apache HŤTPClient library. The JSON response is marshalled into a GiphyRandomAPIGetResponse POJO using Jackson.

请注意org.apache.http.impl.client.CloseableHttpClient(HTTP_CLIENT在下面的代码段中)对象是延迟创建的,每个函数只有一个实例,即,每次调用一个函数时都不会创建一个新对象。

For details, please refer to this section in the Azure Functions How-to guide

....
private static CloseableHttpClient HTTP_CLIENT = null;
....
private static String getRandomGiphyImage(String searchTerm, String apiKey) throws IOException {
    String giphyResponse = null;
    if (HTTP_CLIENT == null) {
        HTTP_CLIENT = HttpClients.createDefault();
        LOGGER.info("Instantiated new HTTP client");
    }
    String giphyURL = "http://api.giphy.com/v1/gifs/random?tag=" + searchTerm + "&api_key=" + apiKey;
    LOGGER.info("Invoking GIPHY endpoint - " + giphyURL);

    HttpGet giphyGETRequest = new HttpGet(giphyURL);
    CloseableHttpResponse response = HTTP_CLIENT.execute(giphyGETRequest);

    giphyResponse = EntityUtils.toString(response.getEntity());

    return giphyResponse;
}
....
GiphyRandomAPIGetResponse giphyModel = MAPPER.readValue(giphyResponse, GiphyRandomAPIGetResponse.class);
Sending the response back to Slack

We extract the required information - title of the image (e.g. cat thanksgiving GIF) and its URL (e.g. https://media2.giphy.com/media/v2Caxw ^LFw4a5y/giphy-downsized.gif). This is used to create an instance of SlackSlashCommandResponse POJO which represents the JSON payload returned to Slack.

    String title = giphyModel.getData().getTitle();
    String imageURL = giphyModel.getData().getImages().getDownsized().getUrl();

    SlackSlashCommandResponse slackResponse = new SlackSlashCommandResponse();
    slackResponse.setText(SLACK_RESPONSE_STATIC_TEXT);

    Attachment attachment = new Attachment();
    attachment.setImageUrl(imageURL);
    attachment.setText(title);

    slackResponse.setAttachments(Arrays.asList(attachment));

最后,我们创建一个HttpResponseMessage(使用流畅的Builder API)。 函数运行时负责将POJO转换为Slack期望的有效JSON有效负载。

return request.createResponseBuilder(HttpStatus.OK).header("Content-type", "application/json").body(slackResponse).build();

For details on the HttpResponseMessage interface, check out the Functions Runtime Java API documentation

在总结之前,让我们仔细研究可能的错误情形以及如何处理它们。

Error handling

General errors

As per Slack recommendation, exceptions in the code are handled and a retry response is returned to the user. The SlackSlashCommandErrorResponse POJO represents the JSON payload which requests the user to retry the operation and is returned in the form of a HttpResponseMessage object.

return request.createResponseBuilder(HttpStatus.OK).header("Content-type", "application/json").body(slackResponse).build();
用户得到一个抱歉,那没用。 请再试一遍。Slack中的消息
User error

用户可能不将搜索条件(关键字)与Slash命令(例如,/有偿。 在这种情况下,用户会在Slack中获得明确的响应-Please include a keyword with your slash command e.g。/有偿 cat。

为此,请求数据(地图检查)是否存在特定属性(文本)-如果没有,我们可以确定用户没有发送搜索关键字。

    ....
HttpResponseMessage missingKeywordResponse = request
.createResponseBuilder(HttpStatus.OK)
.header("Content-type", "application/json")
.body(new SlackSlashCommandErrorResponse("ephemeral", "Please include a keyword with your slash command e.g. /funcy cat")).build();
....
if (!slackdataMap.containsKey("text")) {
return missingKeywordResponse;
}
....



Timeouts

If the call times out (which will if the function takes > than 3000 ms), Slack will automatically send an explicit response - 天哪-该斜杠命令不起作用(错误消息:已达到超时)。 在funcy-slack-app中管理命令。

Resources

下面提到的资源专门用于开发本博客文章中介绍的演示应用程序,因此您可能也会发现它们也很有用!

I really hope you enjoyed and learned something from this article! Please like and follow if you did. Happy to get feedback via @abhi_tweeter or just drop a comment.

from: https://dev.to//azure/code-walkthrough-for-funcy-a-serverless-slack-app-using-azure-functions-3gmd

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值