Webアプリから写真を撮ってGyazoにアップロードするよ via enebular


この記事は enebular Advent Calendar 2020 11日目の記事です。

Webアプリだけで 写真をクラウド上のDBにアップロード ってけっこう面倒じゃないですか?
例えば Gyazo API という写真DBとして使える素敵サービスがありますが、こちらのサーバーはCORSが許容されていないため、Webアプリからの直接アップロードはまず不可能です。

そこで! enebularを使って簡単にAPIアクセスをリレーしてあげるバックエンドを作って置いておくことで、Webアプリからも気軽にGyazoに画像アップロードできるような仕組みを考えてみました。

できたもの

CodePenで実装した、Webカメラからの映像を画像にできるフロントエンド

enebular → Gyazo
という流れになっています。

上記のGIFも「Gyazo GIF」で撮影しました。便利ですね。
※7秒制限に収めるのにちょい苦戦しました。

クイックスタート

  1. Gyazo API アクセストークンを取得します。
    GyazoAPIをNode.jsで使ってみる - Qiita
  2. enebularで以下の公開フローをインポートしてデプロイし、httpNodePathを取得します。
    https://enebular.com/discover/flow/e225e72f-920a-49b5-a71e-11bbd951fbbe
  3. 以下のCodePenを開き、JSの2行目にhttpNodePath+"/upload"、3行目にGyazoAPIのアクセストークンをそれぞれ入力してください。
    https://codepen.io/ukkz/pen/GRqwdLy
  4. GIFを参考にして操作してみよう!

しくみ

フロントエンド

CodePen埋め込みを貼りますが、ちょっと表示上見辛いので上記クイックスタート③のリンクから直接ご覧ください。

See the Pen Gyazoアップロード(enebular経由) by uko (@ukkz) on CodePen.

まずWebカメラから映像を取得し、canvas.context.drawImage()でCanvasに描画し、さらにcanvas.toDataURL()の返り血をimg.srcにセットしています。要はimgタグが用意できさえすれば、別にWebカメラでなくっても普通の画像でもなんでもかまいません。

ここでは以下のように、imgタグの画像情報とGyazo APIのアクセストークンをFormData形式でenebularに送るということをやっています。

// imgタグのsrc(dataURL)から本体部分のみ(Base64文字列)を取得
const base64 = document.getElementById('myimg').src.split(',')[1];
// PNG画像のバイナリ(データ本体)を取得
const binary = atob(base64);
// Uint8Arrayに変換する
const imagedata = (new TextEncoder).encode(binary);
// FormData形式で ①アクセストークン ②画像バイナリ の2点をまとめる
const formData = new FormData();
formData.append('access_token', my_access_token);
formData.append('imagedata', base64);

// axiosでenebularにPOST送信
const res = await axios.post(enebular_url, formData);

// Gyazo APIから返ってきたURLをDOMに反映
document.getElementById('uploaded').href = res.data.url;
document.getElementById('uploaded').innerHTML = res.data.url;

enebular

enebular側にはフローが2系統ありますが、今回は下側の1つのみ利用します。
まずhttp inノードでフロントエンドからのリクエストを受信し、次に禁じ手に近いと言われる functionノードを使って「Base64文字列をUint8Array化したもの」を「Node.jsのBuffer」に変換します。

この時点でmsgは以下のようになっています。

msg.payload = {
  access_token: 'Gyazoアクセストークン文字列',
  imagedata: (Buffer形式の画像バイナリ),
}

ここから、changeノードを使って、Gyazo APIが求める仕様にするためにmsgを以下のように変更します。

msg.headers.content-type = 'multipart/form-data'
msg.payload = {
  access_token: 'Gyazoアクセストークン文字列',
  imagedata: {
    value: (Buffer形式の画像バイナリ),
    options: {
      filename: 'image.png',
    },
  },
}

実際のchangeノードは以下の通り。データを一旦別のプロパティに保存してpayload内を初期化することをやっています。また、ファイル名を指定していないとどうにもアップロードができない模様。

参考:Node-REDからGyazo APIに画像データを保存するメモ – 1ft-seabass.jp.MEMO

あとはhttp requestノードを用いて https://upload.gyazo.com/api/upload にPOSTアクセスし、GyazoレスポンスをJSONオブジェクトとして取り出してhttp responseノードにそのまま入力し、フロントエンド(CodePen)側にHTTPレスポンスを返せば200 OKです。

注意点

  • この記事の方法では、enebular側はデプロイして放置しておくだけでよいです。
    フロントエンド側でGyazoアクセストークンを書き換えることでどんな人でも使うことができます。ただしこれは汎用的なバックエンドとなる反面、ソースコードをのぞけばアクセストークンが丸見えということも意味します。
    (まあそもそもフロントエンドアクセスが考慮されていない仕様ということですね)
    あくまで手軽にプロトタイピングしたいときに使う汎用フローということでご承知おきください。
  • 実用的には、enebular内にアクセストークンを保存して「自分専用のGyazoリレー用バックエンド」とするのがよいでしょう。
  • とはいえ、フローをインポートしてデプロイするだけ、難しく編集する必要がないのがいいところなので、サクッと開発で必要なときにぜひ試してみてください。

まとめ

  • Herokuデプロイすれば永続的な「自分専用CORS対策回避用バックエンド」が作れる!
    ただし、なぜCORSがあるのかということもちゃんと理解しておきましょう。
    なんとなく CORS がわかる...はもう終わりにする。 - Qiita
  • バックエンドをさらっと共有できるのっていいな(enebularの機能ですね)
    プロトアウト スタジオの講師をやっていますが、授業内でこちらの手法を紹介しました。その際にフローをそのまま各生徒に共有でき、ほぼ編集することなしに同様の仕組みを10分程度で全員実現することができました。お世話になりました。