#ruby
#apigateway
#AWSLambda
GoodpatchDay 17

AWS Lambda でRuby のREST APIをブラウザだけで作れるよって話

この記事はGoodpatch Advent Calendar 2018の17日目の記事です。
諸般の事情(家庭内パンデミック!)の影響で記事執筆が遅れたことをお詫びします:bow:

私は普段Ruby on RailsのREST APIのインフラ・バックエンド周りを担当しています。
先日のAWSのイベント AWS re:Invent 2018 で発表された新サービスと機能 | AWS の発表がどれも素晴らしいものばかりでした。
今回はお気軽に試せそうなAWS Lambdaの Ruby対応を試してみましたが、
今までRuby on Railsのみで乗り越えてきた方や、プログラミング初心者の方でも、REST APIやバックエンドで起こっていることについて、理解しやすい感じがしたので、初心者目線で紹介します。
(途中から若干その目線を忘れているかも:sweat:

  • この記事はWebブラウザだけで完結することを縛りにしました。
  • AWSのアカウントは作成済み、Tokyoリージョンにあるものという前提。
  • CloudWatchについては今回触れません。
  • Rubyの Lambda関数AWS Lambda のRubyなので、以降 Lambda という場合は AWS Lambda であると思ってください。

作ってみるもの

クリスマスっぽく、

messageを送るとサンタさんからの簡単なreplyが返ってくる
POST /messages でreplyを返却)

という単純なものにします。

AWS Lambda でRubyを動かす

Lambdaの関数を作成

AWSコンソールにログイン後、Lambda > 関数 > 関数の作成で、今回は1から作成してみます。
Lambda_1.png

以下のように適当に入力します。
ロール名にはあまり意味がありませんが、santaにしておきますか。
Lambda_2.png

できました!早速「テスト」をしてみます

Lambda_3.png

テストイベントを作成

PostMessageなどのテストイベント名にして以下のようなお願いをしてみます。

Lambda_4.png

{
  "message": "Please give me something like special, Santa!"
}

成功!

Lambda_5.png

でも Hello from Lambda! とか言われても子供たちには意味がわかりません。
サンタは何を考えているんでしょうか :thinking:
しかしこれは関数のデフォルトコードを全く変更してないからですねw

Lambdaの関数をCloud9で編集

関数のコードの編集はCloud9というツールで行います。
ブラウザ上から変更可能で意外と便利だなと思いました。
まだRubyのFormatterが動作しないようなので、そこは今後に期待ですね。
Cloud9_1.png

初期のファイルには以下のように書かれています。

lambda_function.rb
require 'json'

def lambda_handler(event:, context:)
    # TODO implement
    { statusCode: 200, body: JSON.generate('Hello from Lambda!') }
end

まず、この関数の引数 event:, context: がどんな情報かを確かめるために一旦以下のようにを全て出力してみましょう。

lambda_function.rb
def lambda_handler(event:, context:)
    res = {
        event: {
            class: event.class,
            inspect: event.inspect,
        },
        context: {
            class: context.class,
            inspect: context.inspect,
        },
    }
    { statusCode: 200, body: res.to_json }
end

ここで間違えやすいのが、ファイルの保存後、右上の「保存」もしないと保存されません。

再度「テスト」を走らせると、以下のようなレスポンスが返ってきていることがわかります。

{
  "statusCode": 200,
  "body": "{\"event\":{\"class\":\"Hash\",\"inspect\":\"{\\\"message\\\"=>\\\"Please give me something like special, Santa!\\\"}\"},\"context\":{\"class\":\"LambdaContext\",\"inspect\":\"#<LambdaContext:0x0000555970e298d8 @clock_diff=xxxxxxxxxxx, @deadline_ms=xxxxxxxxxxxx, @aws_request_id=\\\"xxxxxxxxxxxxxxxx\\\", @invoked_function_arn=\\\"arn:aws:lambda:ap-northeast-1:XXXXXXXX:function:post_message\\\", @log_group_name=\\\"/aws/lambda/post_message\\\", @log_stream_name=\\\"yyyy/mm/dd/[$LATEST]XXXXXXXXXXXX\\\", @function_name=\\\"post_message\\\", @memory_limit_in_mb=\\\"128\\\", @function_version=\\\"$LATEST\\\">\"}}"
}

event はリクエストボディを Hash
context はLambdaの実行環境の LambdaContext が返ってきていることがわかりました。

messageの取得

eventはHashなので、event['message'] でメッセージが取得できそうです。
contextはこの APIでは利用することはないので扱いません。

replyの作成

一旦雑にgeneratorを定義して固定の文言を返します。
これもFile > NewでCloud9で新しいrbファイルを追加して lambda_function.rb と同じフォルダにおいておきます。

reply_generator.rb
class ReplyGenerator
    def self.generate(message)
        'Ho! Ho! Ho!'
    end
end

そしてそれを lambda_function.rb から呼び出すようにしておきます。

lambda_function.rb
require 'reply_generator'

def lambda_handler(event:, context:)
    message = event['message']
    return { statusCode: 400, body: {reply: 'error'}.to_json } if message.nil?
    reply = ReplyGenerator.generate(message)
    res = {
        reply: reply
    }
    { statusCode: 200, body: res.to_json }
end

入力チェックはひとまず簡単にnil判定だけにしちゃいます。(空文字でも通る)

これで「テスト」すると・・・!

{
  "statusCode": 200,
  "body": "{\"reply\":\"Ho! Ho! Ho!\"}"
}

Ho!Ho!Ho!
これでサンタのメッセージが返ってきました。

もうここらで良いかなと思いましたがせっかくなのでインターネットから呼び出したいですよね。

API GatewayでAWS Lambdaを動かす

API Gatewayを設定することでLambdaがインターネットから呼び出せるようになります。

  • AWS LambdaのDesignerからAPIを追加できるのですが、そうするとちょっとややこしかったので、API Gatewayを直接追加していくことにします。

APIの作成

APIゲートウェイへ移動し、APIの作成を行います。
API_1.png
名前は適当に Santa-Message-API とかにしました。

リソースの作成

エンドポイントは messages になるように設定します。
API_2.png

メソッドの作成

POSTメソッドを追加します。
API_3.png

テスト

早速テストしてみましょう!
API_4.png

テストをクリックした後、 リクエスト本文 には以下のような大人のお願いを入れておきます。

{
    "message": "Oh, Santa! Give me more money!"
}

ちゃんと返ってきましたね!
API_5.png

APIのデプロイとステージの作成

ここで「APIのデプロイ」すると、世界中のインターネットからアクセス可能になります。
セキュリティや利用制限など気になる方はAPIキーの設定、またはスロットリングの設定を適切に行なってください。

API>Santa-Message-API>ステージから「作成」します。
API_stage_1.png

ステージの名前は普通はbeta とかproduction とかですがノリで christmas にしておきますか。

API_stage_2.png
できました!

  • 念の為スロットリング設定を10 request/秒で設定しました。それくらいで十分でしょ。

作成したchristmas のステージからメソッドをたどって該当のAPIのURIまで辿り付けます。
こんなURIになります。
https://XXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/christmas/messages

インターネットから試してみる

普段はcurlなどから叩きたいですが、ブラウザだけで完結したいので API Tester という外部サービスを使ってみます。

メッセージには雑に心無いメッセージを入れておきます。
Internet_1.png
できました!

しかしここらでいかなるお願いにもHo! Ho! Ho! で返すサンタに苛立ちを隠しきれません。

ちゃんとメッセージを返す

さて、ここらでさっき適当に作ってしまった、 ReplyGenerator をもう少しだけマシにしましょう。

メッセージをランダムに返す

簡単ですね。

reply_generator.rb
class ReplyGenerator
    REPLIES = [
        'Ho! Ho! Ho!',
        'Thanks for your message!',
        'Hope your enjoy with my present!',
        'Merry Chrinstmas!',
        'I wish Happy New Year!'
    ]
    def self.generate(message)
        REPLIES.sample
    end
end

良くないメッセージは適当に流す

適当に流す文章を作るのに一番時間がかかりました。
フィルタリングは雑にw

reply_generator.rb
class ReplyGenerator
    module Replies
        TO_GOODIES = [
            'Ho! Ho! Ho!',
            'Thanks for your message!',
            'Hope your enjoy with my present!',
            'Merry Chrinstmas!',
            'I wish Happy New Year!'
        ]
        TO_BADDERS = [
            'Hey! Did you mean it? Merry Chrinstmas.',
            'Work hard and keep calm, and hope do your best!',
            'Take a better rest. and I wish your Happy New Year.'
        ]
    end

    CHECK_REGEX = /money|(go away)/

    class << self
        def generate(message)
            detect_reply(message).sample
        end

        private

        def detect_reply(message)
            CHECK_REGEX.match?(message&.downcase) ? Replies::TO_BADDERS : Replies::TO_GOODIES
        end
    end
end

これで振り分けもできましたw

インターネットのエンドポイントからもテスト

以下のようにリクエストすると

{"message": "Hey, Santa! Go away!"}

ちゃんと返ってきましたね。

{"statusCode":200,"body":"{\"reply\":\"Work hard and keep calm, and hope do your best.\"}"}

これでサンタさんは心無い大人にも純粋な子供にもメッセージを返すことができるようになりました :relaxed:

:santa:めでたしめでたし:santa:

まとめ

  • AWS LambdaのRubyは簡単に実行だった
  • Cloud9使えば全てブラウザから編集できる
  • API Gatewayの設定も意外と簡単なので、ブラウザだけで APIが作れる

と、いう初心者向けの内容でした。

  • Rubyはわかるけどインフラよくわからない
  • AWS怖い・・・
  • LambdaってNodeかPythonのものだったよね・・・

という方は是非AWS Lambdaで遊んでみてくださいね。

では Merry Christmas!
HO!HO!HO!