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

この記事は [enebular Advent Calendar 2020](https://qiita.com/advent-calendar/2020/enebular) 11日目の記事です。

Webアプリだけで **写真をクラウド上のDBにアップロード** ってけっこう面倒じゃないですか？
例えば [Gyazo API](https://gyazo.com/api?lang=ja) という写真DBとして使える素敵サービスがありますが、こちらのサーバーは[CORS](https://developer.mozilla.org/ja/docs/Web/HTTP/CORS)が許容されていないため、Webアプリからの直接アップロードはまず不可能です。

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

## できたもの

[![Image from Gyazo](https://i.gyazo.com/c3c927c63ae38469beb7d5dce947cc2e.gif)](https://gyazo.com/c3c927c63ae38469beb7d5dce947cc2e)

CodePenで実装した、Webカメラからの映像を画像にできるフロントエンド
↓
enebular → Gyazo
という流れになっています。

![Screen Shot 2020-12-10 at 23.30.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116011/b55672c0-02d5-9d66-b05f-99dbefd49c02.png)

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

## クイックスタート

1. Gyazo API アクセストークンを取得します。  
  [GyazoAPIをNode.jsで使ってみる - Qiita](https://qiita.com/n0bisuke/items/10f57597787588aa62a0)
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埋め込みを貼りますが、ちょっと表示上見辛いので上記クイックスタート③のリンクから直接ご覧ください。

<p class="codepen" data-height="480" data-theme-id="dark" data-default-tab="js,result" data-user="ukkz" data-slug-hash="GRqwdLy" style="height: 480px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="Gyazoアップロード（enebular経由）">
  <span>See the Pen <a href="https://codepen.io/ukkz/pen/GRqwdLy">
  Gyazoアップロード（enebular経由）</a> by uko (<a href="https://codepen.io/ukkz">@ukkz</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

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

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

```js
// 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

![Screen Shot 2020-12-10 at 23.40.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116011/b94945bc-58ae-a5cc-8ec6-cd0d87cc3dea.png)

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

![Screen Shot 2020-12-10 at 23.41.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116011/72425f9d-067a-8dbb-8669-1a5f1d1ba688.png)

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

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

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

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

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

![Screen Shot 2020-12-11 at 0.22.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116011/22efbc87-d2ab-56ba-7e4d-55a5b2dafca7.png)

参考：[Node-REDからGyazo APIに画像データを保存するメモ – 1ft-seabass.jp.MEMO](https://www.1ft-seabass.jp/memo/2020/02/20/node-red-connect-to-gyazo-api/)

あとは`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](https://qiita.com/att55/items/2154a8aad8bf1409db2b)
- バックエンドをさらっと共有できるのっていいな（enebularの機能ですね）  
  [プロトアウト スタジオ](https://protoout.studio/)の講師をやっていますが、授業内でこちらの手法を紹介しました。その際にフローをそのまま各生徒に共有でき、ほぼ編集することなしに同様の仕組みを10分程度で全員実現することができました。お世話になりました。