S3にアップロードするファイルのContent-Typeは正確に指定しよう
この記事は株式会社ガラパゴス(有志) Advent Calendar 2024
の3日目です3️⃣
こんにちは。バックエンドエンジニアの大田です。連チャンです。
今回はS3でファイルを公開したときの Content-Type
についてです😀
結論
- アップロード時に指定した
Content-Type
は、(当然だが)そのまま使われる。 - 公開時に間違った
Content-Type
が返されると、クライアント側で困ったことになるかもしれない。 - S3でファイルをアップロードするときは、
Content-Type
を正しく指定しよう。
経緯
ユーザーによって指定された画像URLを取得して外部サービスに渡すような機能を開発したのですが、ある時ユーザーから特定のURLについてエラーになるという問い合わせがありました。
よくある話としてURLが限定公開されていて一般には閲覧不可能であるということがありますが、今回はpublicに公開されているURLでした。
調査してみた
当たり前ですがまずはエラーメッセージを確認しました。
ざっくりいうと「外部サービス側で扱えないフォーマット」のような内容でした。
しかしブラウザで開いても普通に閲覧できますし、ダウンロードしてきてローカルのビューアーで開くこともできました。
対象のURLはS3上のものだったので、再現性を確認する意味でそれをダウンロードして同じバケットに別名でアップロードしたところ、そちらはエラーが発生せずに処理されました。
ということは内部的なエンコードの問題などではなさそうです。
ここでようやくソースコードを読み始めました。
外部サービスを呼び出す際に Content-Type
を指定するのですが、それはユーザーが指定したURLが返すものをそのままセットするようになっています。怪しいです 👀
image/jpg という MIME Type は公式に定義されていない
ブラウザで対象URLの Content-Type
を確認したところ、 image/jpg
という値でした。
どうも調べたところ、この MIME Type は公式に定義されていないようです。[1]
そして呼び出している外部サービスではサポートしていません。
通常JPEGの MIME Type は image/jpeg
なのですが、このURLでは Content-Type: image/jpg
が返されています。
どうもS3ファイルの Content-Type
はアップロード時に指定[2]されたものをそのまま返すらしく、つまりはこのファイルをアップロードしたツールもしくはコマンドで間違った Content-Type
を指定したということのようです。
対応
機能の要件としては任意の画像URLを扱うというものなので、どのようなURLが指定されるかは制御できません。
また画像ファイルとしては正常に開くことができるため、ユーザー側で気付いてもらって対応できるようなものでもありません。
なので、プロダクト側で image/jpg
が指定された場合の例外処理を書くことにしました。
できるだけURLが返す Content-Type
を信じたいので、まずは外部サービスがサポートしている MIME Type を調べてそれに合致するかをチェックします。
サポートされていない値であった場合、 image/jpg
であれば image/jpeg
として外部サービスに渡すようにしました。
おまけとして、上記のどれでもない場合はPythonの mimetypes
モジュールでURLから推定して渡す処理も追加しました。[3]
(正直に言うとこれは気休め感がありますね 🙏)
まとめ
ある程度Proxyのような動きをするプロダクトなのでバリデーションが控えめになっているのですが、こういった泥臭い対応が必要になってしまうのは少し大変だなと感じました。
とはいえS3が公式に定義されていない Content-Type
を許容しているのは性質上当然のことではあるので、やむを得ない部分ではありますよね。
今後 MIME Type を指定するようなシーンが有る時は正しいものを使用するように意識しておきたいですね 😃
Discussion