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のRoute HandlerからCloudflare R2にPUTすると411 MissingContentLengthになる問題と対処

0
Posted at

症状

Next.js 14(App Router)のRoute Handlerで、生成したMP3をCloudflare R2にS3互換APIでアップロードしたところ、次のエラーで失敗しました。

411 MissingContentLength: You must provide the Content-Length HTTP header.

署名には aws4fetch を使っています。同じコードを素のNode.jsスクリプトで実行すると成功するので、署名の問題ではありません。

// Route Handler 内では 411 になる
const signed = await client.sign(url, { method: "PUT", body, headers: { "Content-Type": "audio/mpeg" } });
await fetch(signed); // ← R2: 411 MissingContentLength

原因

Next.jsのサーバー実行環境では fetch がパッチされています。この環境では Buffer のbodyがストリームとして送られ、Content-Length ヘッダーが付きませんでした(chunked転送)。

R2の通常の PUT(マルチパートではないアップロード)は Content-Length が必須なので、411が返ります。

対処:署名はaws4fetch、送信はaxios

aws4fetch は署名だけに使い、実際の送信は Buffer の長さから Content-Length を設定してくれるクライアントに任せます。ここではaxiosを使いました。

import { AwsClient } from "aws4fetch";
import axios from "axios";

const client = new AwsClient({
  accessKeyId: process.env.R2_ACCESS_KEY_ID!,
  secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
  service: "s3",
  region: "auto",
});

export async function uploadToR2(key: string, body: Buffer) {
  const url = `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com/${process.env.R2_BUCKET}/${encodeURI(key)}`;

  // 署名だけ行う(送信はしない)
  const signed = await client.sign(url, {
    method: "PUT",
    body,
    headers: {
      "Content-Type": "audio/mpeg",
      "Cache-Control": "public, max-age=31536000, immutable",
    },
  });

  // 署名済みヘッダーをそのまま使って axios で送る(Content-Length が付く)
  await axios.put(url, body, {
    headers: Object.fromEntries(signed.headers.entries()),
    maxBodyLength: Infinity,
    timeout: 120000,
  });
}

ポイントは2つです。

  • client.sign() は署名済みの Request を返すだけで、送信はしません。そのヘッダー(authorization、x-amz-date、x-amz-content-sha256 など)をaxiosに渡します。
  • 署名時と同じ body を送るので、x-amz-content-sha256 と中身が一致し、署名エラーにはなりません。

ハマりどころ:フォールバックが失敗を隠す

私のコードは、アップロードに失敗すると外部APIが返す一時URLをそのまま保存していました。そのため数日間は問題なく再生でき、一時URLの期限が切れた時点で初めて404になりました。

フォールバックする処理がある場合は、フォールバックしたこと自体をエラーログに残しておくことをおすすめします。

まとめ

  • Next.jsのサーバー環境の fetch でR2に PUT すると、Content-Length が付かず411になることがある
  • 署名は aws4fetch、送信は Content-Length を付けるクライアント(axiosなど)に分けると解決する
  • 同じコードがNext.jsの外で動くかどうかを試すと、原因の切り分けが早い

この構成は、歌詞からAIで曲を作るサービス AISong.Fun で実際に使っています。

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?