艦番号から艦名を調べるAPIを作ってみた

# はじめに

数ヶ月前に、職場の後輩が体調不良になりました。
部署は違えども心配になって聞いてみると、どうやら「いつまでも終わらない受託案件」に心が折れてしまったようでした。

私は良いアドバイスが出来るような立派な人間ではありませんし、このコロナ禍のご時世だとランチに誘うことも憚られます。
それでも自分なりに何か出来るのではないかと色々と考えてみた結果、後輩の好きな海上自衛隊の艦船に絡めたプログラムを作ろうと思い立ちました。

# 仕様

* パラメーターとして艦番号を渡すと、その艦番号に該当する艦名などの情報を返すAPIです。
 * 私の実力だとこの程度が限界です...
* APIの仕様はいたって単純で、使えるパラメーターは艦番号を示す`code`ただ1つです。

`https://{APIのドメイン}?code={艦番号}`

* 返却されるデータはJSON形式で、以下のように単純な構造となっています。

```json:レスポンスのJSONの例
{
	"code": "183",
	"name": "いずも",
	"class": "「いずも」型",
	"hullCode": "護衛艦（DDH）"
}
```

# システムの全体像

* 以下の図のように、API GatewayとLambdaを組み合わせただけの非常に単純なシステムです。
* 後述する艦艇データをS3などに置いた方がスマートだと思いますが、今回はパパっと作ることを優先して艦艇データはLambda内に配置しました。

![war-ship.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312166/a9e893e2-419a-0ff8-593a-fd64ff0af9e5.png)

# 艦艇データの用意

* 海上自衛隊の「[艦番号一覧表](https://www.mod.go.jp/msdf/equipment/ships/list/)」というページの内容をコピペして、以下のようなCSV形式に変換しました。

```text:ships.csv
number,name,class,hull_code
91,はしだて,「はしだて」型,特務艇（ASY）
101,むらさめ,「むらさめ」型,護衛艦（DD）
102,はるさめ,「むらさめ」型,護衛艦（DD）
103,ゆうだち,「むらさめ」型,護衛艦（DD）
104,きりさめ,「むらさめ」型,護衛艦（DD）
105,いなづま,「むらさめ」型,護衛艦（DD）
...
```

* このCSVファイルは、後述するLambda関数（lambda_function.rb）と同じ階層に配置しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312166/b99fa618-4c9c-ea40-da96-beccb5bccdbd.png)

# Lambda関数の作成

* 「GetWarShipByCode」という名前で、Ruby 2.7の関数を作成しました。
 * アーキテクチャは`x86_64`を選択しました。
* `event['queryStringParameters']['code']`で、URLパラメーター`code`の値を取得しています。
* その後に上記の`ships.csv`を読み込んで、URLパラメーター`code`の値とマッチするデータをJSON形式に加工して変換しています。
 * マッチするデータが無い場合は、エラーメッセージを返します。
* `Access-Controll-..`という名前のヘッダを返しているのは、後で出てくるCORSに対応するための箇所です。

```ruby:lambda_function.rb
require 'json'
require 'json'
require 'csv'

def lambda_handler(event:, context:)
    # TODO implement
    #{ body: event.to_s }
    
    code = event['queryStringParameters']['code']

    json_hash = nil
    
    CSV.foreach("./ships.csv") do |row|
        # 行に対する処理
        if row[0]==code
            json_hash = {
                "statusCode": "200", 
                "headers": {
                    "Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token",
                    "Access-Control-Allow-Origin": "*",
                    "Access-Control-Allow-Methods": "GET,OPTION"
                },
                "body": JSON.dump({"code": row[0], "name": row[1], "class": row[2], "hullCode": row[3]})
                
            }
        end
    end
    
    if json_hash.nil?
        json_hash = {
            "statusCode": "503", 
            "headers": {
                "Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token",
                "Access-Control-Allow-Origin": "*",
                "Access-Control-Allow-Methods": "GET,OPTION"
            },
            "body": JSON.dump({"errorMessage": "The specified code was not found."})
        }
    end
    
    return json_hash
end
```

# API Gatewayの設定

## APIの作成

* API Gatewayの画面を開き、[APIを作成]を押してRest APIを選択（構築）します。
* プロトコルはREST、新しいAPIを選択し、API名をwarshipに設定します。
 * エンドポイントはリージョンに設定しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312166/e30c8e16-8b33-91ca-b181-4529b53daa8b.png)

## リソースとメソッドの追加

* APIを作成した段階では何も無いので、[アクション]を押してリソースとメソッドを追加していきます。
 * リソースとは`https://{apiのドメイン}/{リソース名}/`のリソース名に該当する部分のことです。
 * メソッドはHTTPのメソッドのことで、GET、POSTなどのことです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312166/2189ec1f-da29-1cfd-6c02-904c1da2bad9.png)

* 今回はお試しなので、最初から存在するリソース`/`に対して、GETメソッドを追加しました。
 * そのため、今回はリソースを作成していません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312166/b2cb8551-39b6-6d3b-d9c9-db8da875f612.png)

* GETメソッドを追加するとセットアップ画面が表示されるので、以下の要領で設定します。
 * 統合タイプ：Lambda関数
 * Lambdaプロキシ統合の利用：YES
 * Lambdaリージョン：Lambda関数を作ったリージョン
 * Lambda関数：先程に作成した関数名
 * デフォルトタイムアウトの仕様：YES（※デフォルト値）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312166/93beccb0-a5f8-3bcb-f619-33b0e2ae37a4.png)


## URLクエリ文字列パラメーターの指定

* Lambda側にURLパラメーターを渡すため、以下の画面で［メソッドリクエスト］を押してメソッドリクエストの編集画面に入ります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312166/688c6814-5d04-f847-1af4-eb02a5bb1303.png)

* ［クエリ文字列の追加］を押して、`code`というパラメーターを追加します。
 * 仕様上は必須パラメーターとなりますが、ここでは［必須］にチェックを入れていません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312166/7e6ebee6-5f5c-a878-06e0-32168d9a52ec.png)

## CORSの有効化

※API Gatewayに独自ドメインを付けて、同一ドメインからアクセスする場合には不要な設定となります。

* 一般的なブラウザはCSRFなどの対策のため、異なるドメインのリソースへのアクセスに制限がかけられています。
* この問題を解決するため、［アクション］から［CORSの有効化］を押してCORSを有効化します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312166/9ea3f3a2-8a6d-b216-c58b-811d563ec6f0.png)

* ［CORSの有効化］を押すと以下のポップアップが表示されるので、［はい、既存の値を置き換えます］を選択します。
* 他にも設定が必要だと書いているサイトもありましたが、私の場合はこれでCORSの問題を解決できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312166/b2d4d7a5-b94b-b292-b104-9f2743a94217.png)



## デプロイ

* API Gatewayの画面左上にある［アクション］ボタンを押して、APIをデプロイします。
* まだデプロイ先の「ステージ」を作成していないので、［デプロイされるステージ］を［新しいステージ］に設定して、ステージ名を指定した上でデプロイします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312166/4c1a7c9b-b713-1fe7-3868-17abd8473f14.png)

# テスト実行

* API Gatewayの［ステージ］画面に入り、作成したステージのエディタ画面に入ります。
* このエディタ画面の上部には、APIを呼び出すためのURLが記載されているので、これを利用してテスト実行してみます。
 * 今回のAPIには`code`パラメーターが必要なので、実際にテストするURLは`https://xxxxxx.execute-api.ap-northeast-1.amazonaws.com/{ステージ名}?code={艦番号}`となります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312166/fbfee0d1-fbfe-a063-8f77-2811e8f4e4ab.png)

* 今回のAPIはGETメソッドなので、ブラウザ上でURLを叩くと以下のようにJSONが返されることを確認できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312166/b174ca4c-f33a-8728-e144-7ad51f284574.png)


# 出来上がった「なんちゃってAPI」を後輩に見せてみた

後輩に「API GatewayとLambdaで、お試しのAPIを作ってみました」と伝えて、`code=181`のパラメーターを付けたURLを送ったところ、早速「おお、お試しのおふねAPIでしたか！」と返事がきました:relaxed:

しかも、URLを見た瞬間に「番号的にもしやと思ったんですが、ばっちり"ひゅうが"ですね」と最初からバレてました:innocent:
（"ひゅうが"の艦番号は181です）

これで少しだけでも気分転換になっているといいな...
（そして私もAWSの勉強が少しできて良かった...）

# 本当はこうしたかった

* APIに独自ドメイン名を付与したかったのですが、今回はそこまで手が回りませんでした。


# 参考URL

* [艦番号一覧表](https://www.mod.go.jp/msdf/equipment/ships/list/)
* [ゼロから作りながら覚えるAPI Gateway環境構築](https://dev.classmethod.jp/articles/getting-start-api-gateway/)