はじめに
LiveView が盛り上がっている中 REST API かよ、という感じですが、画像 AI を提供するエンドポイントを REST API として提供する形態を想定しています
参考にした Elixir Forum の質問
JSON データで受信する
特に何も考えずに Phoenix で REST API を構築すると、 Controller でリクエストを受ける実装は以下のようになります
defmodule ApiWeb.PredictionController do
use ApiWeb, :controller
action_fallback ApiWeb.FallbackController
# param に Map でリクエストが入ってくる
def index(conn, param) do
...
end
...
end
例えば curl で以下のように送信したとします
curl -XPOST http://localhost:4000/api \
--data "{\"xxx\":\"aaa\"}" \
--header "Content-Type:application/json"
すると JSON がデコードされ、 param
には以下のような Map が入ってきます
%{"xxx" => "aaa"}
ここに JSON ではなくバイナリデータを送ってみます
curl -XPOST http://localhost:4000/api \
--data-binary @sample.jpg \
--header "Content-Type:image/jpeg"
そうするとデコードできないので、空の Map が入ってくるようになります
%{}
JSON 形式で画像データを送信したい場合、画像を文字列に変換する必要があります
というわけで、よく BASE64 が使用されます
curl で BASE64 エンコードした画像を以下のように送信します
echo {\"image\":\"$(base64 -i sample.jpg)\"} | \
curl -XPOST \
--data @- \
--header "Content-Type:application/json"
ちょっと解説
-
base64 -i <画像ファイルパス>
で画像を base64 文字列に変換できます -
$(<シェルコマンド>)
でシェルコマンドの実行結果を値としてシェルコマンドの中に展開します -
echo {\"image\":\"$(base64 -i sample.jpg)\"}
で{"image":"<画像のBASE64変換結果>"}
を標準出力に渡します(\
はエスケープ文字で、"
を出力するために使っています) -
|
(パイプ)で標準出力を次のシェルコマンドに渡します (Elixir でいうところの|>
) -
@-
で|
で渡ってきた値を受けます(つまり、{"image":"<画像のBASE64変換結果>"}
が JSON として送信される)
この場合、 Controller では以下の様にパターンマッチして受け取り、 Base.base64
でデコードすることができます
...
def index(conn, %{"image" => base64}) do
binary = Base.decode64!(base64)
...
end
...
この実装で確かに画像データを REST API で受けて処理することは可能です
しかし、いちいち BASE64 エンコードとデコードを挟むのは無駄ですよね
というわけで本題です
バイナリデータのまま受信します
バイナリデータで受信する
答えは以下の様な実装になります
curl 送信
curl -XPOST http://localhost:4000/api \
--data-binary @sample.jpg \
--header "Content-Type:image/jpeg"
Phoenix 受信
...
def index(conn, %{}) do
{:ok, binary, _} = Plug.Conn.read_body(conn)
...
end
...
Plug.Conn.read_body
で接続 conn
を読み込むと、そのままバイナリデータを取得することができます
まとめ
すっごく単純な答えでしたが、普通は JSON しか送受信しないのでなかなか辿り着きませんでした