0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Next.jsのBFFから画像を送信したら届きそうで届かなかった話

Posted at

Screenshot 2024-02-13 at 17.59.04.png

この記事を読んだ方がいい人

Javascriptに不慣れな人。

HTMLで当たり前のように遅れていた画像がBFFを通したら送れなくて困っている人

この記事を読まない方がいい人

コーディングとネットワーク領域の両方に自信があるひと

やりたかったこと

Next.jsで作ったフロントアプリケーションにて画像登録のフォームを作成した。

バックエンドAPIはGoで作ってある

はまったポイント

BFFで画像をハンドリングする処理がうまくいかない。

そもそもフォームデータって

フォームって一言で言っても画面UIとしての入力フォーム的なこととも言えるし、もう少し物理層チックなことを言ってそうな気もしますが、今回はHTTP通信においてのテキストとしてのフォームのことです。

フォームをブラウザから送信すると中身はこんな感じの区切り文字と値で表されるテキストになっています。


------WebKitFormBoundaryH1hEBwyt0daM3FqD
Content-Disposition: form-data; name="profileImage"; filename="Screenshot 2024-00-00 at 00.55.43.png"
Content-Type: image/png


------WebKitFormBoundaryH1hEBwyt0daM3FqD
Content-Disposition: form-data; name="email"

mail.address@example.com
------WebKitFormBoundaryH1hEBwyt0daM3FqD
Content-Disposition: form-data; name="password"

12345abcde
------WebKitFormBoundaryH1hEBwyt0daM3FqD
Content-Disposition: form-data; name="birthOfDate"
・
・

busboyが使えない

busboyというのはどんな少年なのかと思った方もいるかもですが、Javascriptでmultipartのリクエストを解釈するためのライブラリです。

本気の情報をお求めの方は公式へどうぞ。

最近awaitとかasyncとかPromiseし始めたJS素人童貞の自分からすると、このコールバック祭りなコードはハードルが高かったです。

受け取ったフォームデータの処理を定義しておいて、req.pipeに渡す処理。

busboy.ts
function processFiles(req: NextApiRequest): Promise<any> {
  return new Promise((resolve, reject) => {
    const bb = new Busboy({ headers: req.headers });
    const result = { files: [], fields: {} };

    bb.on('file', (fieldname, file, filename, encoding, mimetype) => {
      console.log(`File [${fieldname}]: filename: ${filename}, encoding: ${encoding}, mimetype: ${mimetype}`);
      const buffers = [];

      file.on('data', (data) => {
        buffers.push(data);
      });

      file.on('end', () => {
        result.files.push({
          fieldname,
          filename,
          encoding,
          mimetype,
          data: Buffer.concat(buffers), // ファイルデータを結合
        });
      });
    });

    bb.on('field', (fieldname, val) => {
      result.fields[fieldname] = val;
    });

    bb.on('finish', () => {
      resolve(result);
    });

    bb.on('error', (error) => {
      reject(error);
    });

    req.pipe(bb);
  });

このライブラリはUnexpected EOF的なエラーが解消できず使うのを断念しました。

フォームを受け取る前に次の処理が始まっていそうにも見えたのですが覚えたてのasync awaitをこねくり回してもダメだったので一旦捨てました。

つまりフロントエンド→BFFで止まった状態です。

formidableでBFFを突破

ライブラリを変えてformidableで実装し直し。

formidable.ts
  return await new Promise((resolve, reject) => {
    // formidableを使用してファイルとフィールドをパース
    const form = new IncomingForm()
    form.parse(req, async (err, fields, files) => {
      if (err) return reject(err)

      const file = files.profileImage[0]
      const fileContent = await fs.readFile(file.filepath)
      const formData = new FormData()

      // 画像の場合はファイル名とMIMEタイプを指定しないとバックエンドで正しく読み込めない
      formData.append('userProfileImage', fileContent)

      formData.append('email', fields.email[0])
      formData.append('password', fields.password[0])

これでGoのAPIまで到達したことは確認できました。

でもデバッグすると画像の実態は届いていそうなのですが、画像を取り扱おうとすると画像がない状態に。

backendApi.go
file, err := c.FormFile("profileImage") // これがエラーになる

リクエスト情報をデバッグするとバイナリ文字がブァーっと出力されるので実態はきていそうなのだが。

ちなみにPostmanからは正常に送れ登録できるのでクライアント側に原因がありそう。

こんな感じでファイルもフォーム送信できる。優秀。
Screenshot 2024-02-13 at 18.35.37.png

解決

formidableModified.ts
  return await new Promise((resolve, reject) => {
    // formidableを使用してファイルとフィールドをパース
    const form = new IncomingForm()
    form.parse(req, async (err, fields, files) => {
      if (err) return reject(err)

          const file = files.profileImage[0]
      const fileContent = await fs.readFile(file.filepath)
      const formData = new FormData()

      // 画像の場合はファイル名とMIMEタイプを指定しないとバックエンドで正しく読み込めない


      // appendの第3引数!!!!
      formData.append('userProfileImage', fileContent, {
        filename: 'userProfileImage.jpg',  // これとか
        // contentType: type.mime, // 指定する場合は動的に取得する // これはなくてもよかた。
        knownLength: fileContent.length // これとか
      })

      formData.append('email', fields.email[0])
      formData.append('password', fields.password[0])

formにappendする際、付加情報みたいなものを第3引数に取れるようなのですが、これを追加することで正しくハンドリングできました。

contentTypeは画像データから動的にimage/jpegだのimage/pngだのやるのがだるかったので消してみたら動きました。

ちなみに頼りになるパイセンに相談した結果この答えまで辿り着いたのですが、ここが怪しいって微塵も想像してなかったので実力不足を突きつけられたお題でした。。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?