RailsでFace APIを扱うので噛み砕いてみた◎

## はじめに
RailsでFace APIを扱う記事が少なく実装に一手間かかってしまったので、
初歩的ですが今後同じように開発する方がスムーズに実装できるようにこの記事が手助けになれば幸いです！m(_ _)m

## この記事に期待できること

- RailsでFace APIを扱うためのコードを理解できる
- リクエストするデータについての理解が深まる(おまけ)

前提としてREST APIについての知識があればより理解してもらえると思います。

## Face APIについて
本題に入る前にFace APIとは？の説明をしておきます。

Microsoftが提供するAzure APIと呼ばれるサービスの一つです。

公式サイトは[こちら](https://azure.microsoft.com/ja-jp/services/cognitive-services/face/)
Face API詳細については[こちら](https://westus.dev.cognitive.microsoft.com/docs/services/563879b61984550e40cbbe8d/operations/563879b61984550f30395236)

実装にあたりAPIキーを必要とするのでアカウント登録をしておく必要があります。
登録方法は沢山記事があるのでそちらを参考にしてください。
私は[この当たりの記事](https://zenn.dev/tmitsuoka0423/books/939110f3e1f533d35095/viewer/03_faceapi_issue_key)を参考にしたと思います。

今回は感情データのみ扱いますが、
もちろん感情データ以外の取得も可能なので詳しくは公式を確認して下さい。

## やりたいこと
本記事では任意の画像データをDBに保存しないで直接APIリクエストし、
感情データのレスポンスを取得する方法を説明したいと思います。  

こんな感じ◎
※添付データはもちろん顔写真
[![Image from Gyazo](https://i.gyazo.com/55d1fd0e4a6b292d22b58c005b107722.gif)](https://gyazo.com/55d1fd0e4a6b292d22b58c005b107722)

見易くすると以下のようなデータを取得します↓

```json:JSON形式
[
    {
        "faceId": "c5c24a82-6845-4031-9d5d-978df9175426",
        "faceRectangle": {
            "width": 78,
            "height": 78,
            "left": 394,
            "top": 54
        },
        "faceAttributes": {
            "emotion": {
                "anger": 0.575,
                "contempt": 0,
                "disgust": 0.006,
                "fear": 0.008,
                "happiness": 0.394,
                "neutral": 0.013,
                "sadness": 0,
                "surprise": 0.004
            },
        }
    }
]
```

## 完成コード
まずは結論！（これで動くはず〜）
※エンドポイントとかAPIキーは各々違うので書き換えて下さいね。

```ruby:Gemfile(追加)
gem 'slim-rails'
gem 'dotenv-rails'
```

```ruby:routes.rb
Rails.application.routes.draw do
  root 'home#top'
  post 'check', to: 'check#result'
end
```

```ruby:top.html.slim
= form_with url: check_path do |f|
  = f.file_field :image
  = f.submit 'check'
```

```ruby:check_controller.rb
class CheckController < ApplicationController
  def result
    # ①URI指定
    uri = URI.parse("https://japanwest.api.cognitive.microsoft.com/face/v1.0/detect")
    uri.query = URI.encode_www_form({
      "returnFaceAttributes" => "emotion"
    })

    # ②https通信の準備
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = uri.scheme === "https"
    
    # ③画像データをバイナリで渡す
    body = params[:image].read
    
    # ④バイナリ&APIキーを指定
    headers = { 
      "Content-Type" => "application/octet-stream",
      "Ocp-Apim-Subscription-Key" => ENV["API_KEY"]
    }

    # ⑤POSTした引数データのレスポンスを代入
    response = http.post(uri, body, headers)
    
    # ⑥JSON形式でレスポンスを確認
    render json: response.body
  end
end
```
## コードを噛み砕く
FaceAPIに関わるコントローラー箇所だけ補足説明していきます。
因みに公式のサンプルコードだと何故か動かなかったので[こちらの記事](https://qiita.com/takano-h/items/dd10818eb7e09161bc29)を参考に違う書き方をしました。
（何故だったのか未だに分かっていない、、）

### ①URI指定
```ruby
URI.parse
```
引数のURLをURIオブジェクトとして生成しています。
※FaceAPI登録時のエンドポイントの設定によってURLは変わります。

```ruby
URI.encode_www_form
```
引数から`application/x-www-form-urlencoded`(Content-Type ※後述)形式の文字列を生成しています。

```ruby
"returnFaceAttributes" => "emotion"
```
FaceAPIから感情取得するためのハッシュです。
他にも色々あるので気になる方は公式で確認して下さい。

```ruby
uri.query
```
クエリ（上記で生成された`returnFaceAttributes=emotion`）を追加して
`https://japanwest.api.cognitive.microsoft.com/face/v1.0/detect?returnFaceAttributes=emotion`
を生成しています。

### ②https通信の準備

```ruby
Net::HTTP.new
```
Net::HTTPオブジェクトを生成しています。

```ruby
uri.host
```
ホスト名（ここでは`japanwest.api.cognitive.microsoft.com`）が返ります。

```ruby
uri.port
```
ポート番号（ここではhttpsの`443`）が返ります。

```ruby
uri.scheme === "https"
```
`uri.scheme`が`"https"`であるか(真偽値)を返しています。

```ruby
http.use_ssl
```
（`true`の場合）https通信ができるようになります。

### ③画像データをバイナリで渡す

```ruby
params[:image].read
```
`f.file_field :image`で添付された画像データを読み込んでいます。

### ④バイナリ&APIキーを指定
```ruby
"Content-Type" => "application/octet-stream"
```

`"Content-Type" `はデータの種類を示す情報を指定する項目のこと。
※Face APIでは`"application/octet-stream"`（バイナリ指定）、
もしくは`application/json`（url指定）のみ使用できることが公式に明記されています。

```ruby
"Ocp-Apim-Subscription-Key" => ENV["API_KEY"]
```
Face API登録時に発行したAPIキーを指定しています。
実際のキーは`gem 'dotenv-rails'`で.envファイルに記載してセキュリティ対策しています。

### ⑤POSTした引数データのレスポンスを代入
```ruby
http.post(uri, body, headers)
```
ここまで準備した変数達をFace APIにリクエストしています。
WEB APIにはPOSTリクエストしてもGET相当の結果が返ってくるものもあるんだとか。

### ⑥JSON形式でレスポンスを確認
```ruby
render json: response.body
```
renderメソッドは呼び出すテンプレート（ビュー）ファイルを指定しますが、
オプションにjsonをつける事で指定したオブジェクト(`response.body`)をJSON形式のデータにして返しています。

```ruby
response.body
=> "[{\"faceId\":\"73ec49e2-e53c-49ef-b8e1-4e9640907434\",\"faceRectangle\":{\"top\":118,\"left\":116,\"width\":168,\"height\":168},\"faceAttributes\":{\"emotion\":{\"anger\":0.0,\"contempt\":0.0,\"disgust\":0.0,\"fear\":0.0,\"happiness\":0.0,\"neutral\":0.998,\"sadness\":0.001,\"surprise\":0.0}}}]"
```
ちゃんと返ってきてますね◎

## まとめ
お手紙のやり取りに例えると

- ①宛先書いて
- ②ハガキではなく封筒で
- ③手紙本文入れて
- ④見出し&添え書きして
- ⑤郵送受け渡し
- ⑥お返事を開封

といった感じに近いでしょうか。

## 個人的にハマったこと（おまけ）

- バイナリデータをテキスト化して読み込ませてしまっていた(´Д` )

```ruby:check_controller.rb
body = Base64.strict_encode64(params[:image].read)
```
これは間違い×

（以下[ウィキペディア](https://en.wikipedia.org/wiki/Base64)参照）
> Base64 is a group of binary-to-text encoding schemes that represent binary data (more specifically, a sequence of 8-bit bytes) in an ASCII string format by translating the data into a radix-64 representation.

Base64はバイナリをテキストエンコーディングしてるものですよ〜とばっちり書いてありました！
この違いを理解していなくて一生Base64でリクエストしていました（笑）

因みに

```ruby:check_controller.rb
body = params[:image].read
```
の中身は

```ruby
=> "\xFF\xD8\xFF\xE0\x00\x10JFIF\x00\x01\x01\x00\x00H\x00...
```
のようになり、

```ruby:check_controller.rb
body = Base64.strict_encode64(params[:image].read)
```
の中身は

```ruby
=> "/9j/4AAQSkZJRgABAQAASABIAAD/4UvnRXhpZgAATU0AKgAAAAgAB...
```
のようになります。

全然違いましたね^^;

## おわりに
今回はFaceAPIの本当に導入だけの内容でしたが、
何となく動いた〜で終わらず噛み砕くことも大事だと思ってまとめてみました。
もし誤った記載があればご指摘頂けると幸いです！

## 参考記事
- [Face API - v1.0（公式）](https://japanwest.dev.cognitive.microsoft.com/docs/services/563879b61984550e40cbbe8d/operations/563879b61984550f30395236/console)
- [RubyのHTTPリクエストをできるだけシンプルに実装する](https://qiita.com/takano-h/items/dd10818eb7e09161bc29)
- [uriライブラリ 簡易まとめ](https://qiita.com/mk_0409/items/1775a1b335bc97bf5189)
- [Railsでhttps通信を使ってjsonでPOSTする](https://www.y-hakopro.com/entry/2020/03/07/181532)
- [Rubyの===演算子って何？](https://qiita.com/Siva0410/items/c0b6ed52017ffada8bf4)
- [Base64 - Wikipedia](https://en.wikipedia.org/wiki/Base64)
- [How to send binary data through a HTTP request using a Ruby gem?](https://stackoverflow.com/a/24537498)