🐥
Cloud RunのHTTP/1レスポンス最大サイズの課題と回避策
皆んな大好きCloud RunのTipsです。コミューンアドベントカレンダー2024の16日目です。記事書いている時点では19日なのでもう今年二週間も残っていません、駆け込みで何かを成さねばという使命感に追われています。
HTTP/1におけるレスポンスの最大サイズには32MiBという制限が存在します。この制限は特に大きなファイルを返すAPIを構築する際に問題になることがあります(ありました)。CloudRunに立てたExpressでファイルを配信する際に問題にこの制約に引っかかったのでメモがてら対応を残します。
- HTTP2にする
サイドカーを使ってhttp2対応する。下記記事に詳しく書かれてます。今回は結局この方法で対応しました。
https://zenn.dev/dev_commune/articles/06e91015090680 - Transfer-Encoding: chunked もしくはストリーミングメカニズムを使用する
今回の問題の方法です。
結論、octet-streamでstreaming配信されていても、Content-Lengthの設定値が32MiBを超えている場合、レスポンスエラーになります。
以下詳細
公式ドキュメントには次のように記載されています:
Transfer-Encoding: chunked またはストリーミング メカニズムを使用しない場合は 32 MiB
一見するとストリーミング形式で配信すればこの制限は回避可能であるように読めます。しかし、実際には Content-Type: application/octet-stream
で静的ファイルを返す場合も、レスポンスサイズが32MiBを超えるとエラーが発生します。
問題の発生するコード
const express = require('express');
const fs = require('fs');
const app = express();
app.get('/download', (req, res) => {
const filePath = './large-file.txt'; // e.g. 35MiB
const stat = fs.statSync(filePath);
res.set('Content-Type', 'application/octet-stream');
res.set('Content-Length', stat.size);
const readStream = fs.createReadStream(filePath);
readStream.pipe(res);
});
const port = process.env.PORT || 8080;
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
この状態だとストリーミング配信になっており、1回のレスポンスサイズが32MiBを超えていないため問題なさそうに見えます。しかしContent-Lengthが32MiBを超えているためこの状態だとエラーが出ます。
そのため、この状況で取れる手が
- Transfer-Encoding: chunked を設定する
- Content-Lengthのプロパティを消す
になります。が、基本的に2の選択肢を取るシーンはほぼないと思うので1で良さそうです。
ストリーミング配信していてもCloud Runのレスポンスサイズ制限を受けることがある話でした。
Discussion