【React/Express】画像・音声等のバイナリデータをHTTP通信でサーバーサイドに送信する

# 目標
クライアントがアップロードした画像データをサーバーサイドに送信する


# やり方

色々やり方はあるが、今回は```'Content-Type': 'application/octet-stream'```を使用。

:::note
```'Content-Type': 'application/octet-stream'```
任意のバイナリデータ（＝"汎用のバイト列"）を送信するためのMINEタイプ
:::

これを使用すると、ブラウザ側のblobを直接body部に指定してバイナリデータを送信できる。

:::note warn
『blob』はあくまでブラウザ側のjavascriptでバイナリを扱う形式
サーバーサイド側で、『buffer』という別形式に変換されることに注意(後述)

⭐blobはバイナリとコンテンツタイプデータを持つが、bufferはコンテンツタイプデータを持たない。
　そのため、image/jpegのようなデータもセットで送信しなければ復元が難しくなる。

⭐参考記事
- https://qiita.com/tanakahb/items/3714683b01642d907cd9
:::


## React側コード例
```javascript
const res = await fetch('/submitdata', {
method: 'POST',
headers: {
        'Content-Type': 'application/octet-stream'
        'X-type ':upload_image.type, // Blobのコンテンツタイプ
    },
    body: upload_image // Blob or File
});
```
upload_imageはblob（画像）です。
Fileでも送信できますが、今回はBlobでやります。


## Express側コード例

```express.js
app.post('/submitdata',(req, res) => {
    // console.log('Request Headers:', req);
    // console.log(req.headers['x-genre'])
    // console.log(req.headers['x-description'])
    console.log(body);
    res.json({
      body
    });
})
```

実際に送信して、Express側のコンソールを確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3801383/a3c74308-e6c2-48a9-bb95-49996eb59a6b.png)

Bufferデータとして無事届いている。
あとはこのバイナリデータをDBやストレージに保存すればよい。


## BufferデータをBlobに変換
Bufferデータはブラウザ側のjavascriptに適した形式ではないため、Blobに変換が必要

下記のコードで変換可能(jpegと仮定)

```javascript
const uint8Array = new Uint8Array(buffer);
const blob = new Blob([uint8Array], { type: 'image/jpeg' });
```

## 検証
下記コードでbufferをblob変換してURL作成＆srcに指定してブラウザ上で正常に表示されることを確認する
```javascript
const [test,testUpdate] = useState();
const uint8Array = new Uint8Array(responsejson.body.data);
const blob = new Blob([uint8Array], { type: 'image/jpeg' }); // 必要に応じて 'image/png' など
testUpdate(URL.createObjectURL(blob));

// html部分割愛
<img src={test}></img>
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3801383/799b031f-7164-4699-8128-cd4435b39175.png)
↑ばちこり、同一画像であることが確認できた


# 応用

```'Content-Type': 'application/octet-stream'```では、bodyにバイナリデータしか指定できない。
しかし、画像のメタデータやなんかしらの情報なども一緒に送信したくなる。

そんな時はヘッダーに追加することになる。がHTTPヘッダーは日本語を使用するとエラーになる。

:::note warn
正確には HTTPヘッダーには『ISO 8859-1』準拠の文字列しか扱えない。
日本語などを含めると下記エラーとなる。

![スクリーンショット 2025-05-11 224109.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3801383/4e5a4927-90f8-46bc-a537-2c8d56a9710d.png)
:::


そのため、文字データはURLエンコードした上でヘッダーに乗せる必要があり。


## ヘッダー追加例
今回は
『画像のジャンル』
『画像の説明』
をヘッダーに指定する。

下記コードがヘッダー追加例。

⭐フロント側
```jsx
//ジャンル,説明をエンコードしてヘッダーに乗せる 画像はbodyに乗せる
const genre = "海の画像"
const description = "きれいな海の画像です"
const res = await fetch('/submitdata', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/octet-stream',
        'X-Genre': encodeURIComponent(genre),
        'X-Description': encodeURIComponent(description),
    },
    body: upload_image_blob // Blob or File
});
```

⭐バックエンド側

```jsx
app.post('/submitdata',(req, res) => {
    const decodedGenre = decodeURIComponent(req.headers['x-genre']);
    const decodedDescription = decodeURIComponent(req.headers['x-description']);
    const body = req.body
    console.log(decodedGenre);
    console.log(decodedDescription);
    console.log(body);
    res.json({
      body
    });
})
```

ちなみにバックエンド側ではヘッダー指定する文字列は小文字にしないとエラーになります（理由は知りません）
'X-Genre'　⇒　✖　'x-genre'　⇒　〇



:::note
encodeURIComponent()　⇒　非ASCII文字列をバイト列に変換する。
こうすることで日本語などの非ASCII文字列をヘッダーに乗せて送信できる。
:::

:::note warn
HTTPヘッダーはbodyと比較してサイズ制限が厳しい(8KB~16KBが一般的？）
ヘッダーサイズが大きいとエラー(413)になることに注意。


とはいえ、ヘッダーサイズ上限はWEBサーバーの設定で変えられる上、フロント側で制限すれば問題になることはあまりない印象。(ユーザー入力等を制限なしで受け付けると事故率は高まるが。)
:::