Help us understand the problem. What is going on with this article?

lambda入門(Node)③ - API Gatewayを使ってslackからのリクエストをlambdaで受けられるようにする

More than 3 years have passed since last update.

第3回になりました。

過去のはこちら。

今回は、ようやくやりたいことに近づいて来まして
slackからのリクエストをlambdaで受けられるようにしたいと思います。

何か調べていくと、どうもAPI Gatewayを使うと良い感じぽい。
まずはAPI Gatewayについて予習を。

API Gateway

どんな役割をしてくれるのか

APIのエンドポイントとして待ち構える玄関として使える
現在はhoge/*のようなパスを/hoge/{proxy+}として設定できる
プロキシリソースなるものらしい
post, putの振り分けなどが簡単になった

課金体系

受信した API 呼び出しと、送出したデータ量に対して発生

serverless frameworkでの設定

serverless.yml
functions:
  bookStore:
    handler: books/store.store
    events:
      - http:
          path: books
          method: get
          cors: true

これだけ。
デプロイ実行すると

$ serverless deploy -v --stage dev
Serverless: Packaging service...
・
・
CloudFormation - CREATE_IN_PROGRESS - AWS::ApiGateway::Deployment - ApiGatewayDeployment1490662361327
CloudFormation - CREATE_IN_PROGRESS - AWS::ApiGateway::Deployment - ApiGatewayDeployment1490662361327
CloudFormation - CREATE_COMPLETE - AWS::ApiGateway::Deployment - ApiGatewayDeployment1490662361327
・
・
Service Information
service: testProject
stage: dev
region: ap-northeast-1
api keys:
  None
endpoints:
  GET - https://xxxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/books
functions:
  bookStore: testProject-dev-bookStore

エンドポイントが作られた。。!

レスポンスを送れるか試してみる

$ curl https://xxxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/books
{"message":"ok",...........}

うん、okそう、すごい。ymlに書くだけで何でも設定してくれちゃう。

Slackから応答させるようにする

ここで今回の本題。
作成したエンドポイントに対してスラックがリクエストを送って
slack上にメッセージが返ってくるようにする。

全体像

スクリーンショット 2017-04-29 15.17.57.png

やることとしては、リクエストパラメータを解析してデータを保存。
保存した結果を返す

book.js
'use strict';

const slackAuthorizer = require('../authorizer/slackAuthorizer');
const parser = require('../service/queryParser');

const bookSave = require('../useCase/book/save.js');

module.exports.book = (event, context, callback) => {
  const queryParser = new parser(event.body);
  const authorizer = new slackAuthorizer(queryParser.parseToken());

  /**
   * 認証
   */
  if (!authorizer.authorize()) {
    context.done('Unauthorized');
  }

  /**
   * @Todo ここで lambda function の振り分けを行いたい
   */
  bookSave(event, (error, result) => {});

  const response = {
    statusCode: 200,
    body: JSON.stringify({
      message: 'ok',
    }),
  };

  callback(null, response);
};

同期で処理を行いたいので asyncを使って処理をブロックごとに実行する

save.js
'use strict';

const AWS = require('aws-sdk');
const dynamoDB = new AWS.DynamoDB.DocumentClient();
const bookTable = process.env.bookTable;

const uuidV1 = require('uuid/v1');

const webhookUrl = process.env.slack_webhook_url;
const request = require('request');

const dateTime = require('node-datetime');
const dt = dateTime.create();
const insertDate = dt.format('Y-m-d H:M:S');

const async = require('async');

const parser = require('../../service/queryParser');

module.exports = (event, callback) => {

  const queryParser = new parser(event.body);

  const key = uuidV1();

  async.series([
    function(callback) {

      /**
       * データを保存
       */
      dynamoDB.put({
        'TableName': bookTable,
        'Item': {
          'id': key,
          'title': queryParser.parseText(),
          'insert_date': insertDate,
        },
      }, function(err, data) {
        callback(null, "saved");
      });
    },
    function(callback) {

      /**
       * 保存したものを取り出して
       * 結果を返す
       */
      dynamoDB.get({
        TableName: bookTable,
        Key: {
          id: key,
        },
      }, function(err, data) {
        if (!err) {
          const response = {
            text: `\`${data.Item.title}\` is Saved !!`,
          };
        
          /**
           * webhook でチャンネルにメッセージを返す
           */
          request.post(webhookUrl, {
            form: {
              payload: JSON.stringify(response),
            },
          }, (err, response, body) => {
            callback(null, 'getData');
          });
        }
      });
    },
  ], function(err, results) {
    if (err) {
      throw err;
    }
  });
};

slash commandから送られてきたパラメータをパースするために
query-stringモジュールを使い、ラップした

queryParser.js
'use strict';

const queryStringParser = require('query-string');

module.exports = class queryParser {

  constructor (queryString) {
    this.queryString = queryString;
  }

  parseToken () {
    return queryStringParser.parse(this.queryString).token;
  }

  parseText () {
    return queryStringParser.parse(this.queryString).text;
  }
};

slashコマンドの設定は省略します。
最初の api gatewayで設定されたエンドポイントのURLを
設定してあげればいいので

実際に動かすとこんな感じです。

4b4b9687608f58545ef2b4536a7166c2.gif

ソースは汚いかもしれませんが、saverlessフレームワークで
エンドポイント作成やdb、外部サービスの連携が簡単に出来ました

もっとキレイに書けるようにjsの筋力をつけていかねば。。笑

smith-30
engineer elmをやりたいと思っている
elm-jp
主に日本で活動する Elm 利用者のコミュニティです。
https://elm-lang.jp
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away