銀の弾丸

プログラミングに関して、いろいろ書き残していければと思っております。

AWS S3 の putObject API でメタデータを設定する

f:id:takamints:20190415215350j:plain

AWSによるサーバーレスアーキテクチャ
Peter Sbarski
翔泳社
売り上げランキング: 157,672

AWS S3 の putObject APIバケットにファイルをアップロードするときには、メタデータをきちんと指定しておきましょうという話。 例えば、メタデータを設定しておいた既存ファイルに上書きアップロードする場合でも、putObjectでメタデータを指定していなければすべて失われてしまいます。

Content-Type については、AWS-CLIからアップロードする場合は適切なMIME Type を設定してくれるのですが、APIでは自分で指定しなければなりません。でなけりゃすべて application/octet-stream に設定されて、WEBで公開している場合は、すべてがバイナリファイルの扱いになり、HTMLファイルだってなんでもかんでもダウンロードされてしまいます。

バケットをWebで公開設定していたり、CloudFrontから配信しているならば、ブラウザキャッシュを禁止したいておきたい場合がありますが、ファイルを上書きするたびに、その設定は失われます(AWS-CLIでは、--cache-control オプションに、no-cache を指定すればOKです)。

まあ、とにかくAPIでputObjectする場合には必要なメタデータを毎回設定するべきなんですね。

ということで、以下のコードは、APIでS3にファイルをアップロードする際、同時にメタデータを設定するNode.jsのコードです(AWSへの接続はAWS-CLIのプロファイル(~/.aws/ 以下の configcredentials)が正しくセットアップされている前提です)。 ここでは putObject のパラメータで、最低限設定しておきたい Content-Type と、おそらく必要になるであろう Cache-Control を指定しています。

"use strict";
const AWS = require("aws-sdk");
const mime = require("mime-types"); //拡張子からMIME Type
const fs = require("promise-fs"); //地獄に落ちないfsモジュール
const { promisify } = require("es6-promisify"); //callbackの非同期をPromise化
const s3 = new AWS.S3();
const promised = {
    s3: {
        putObject: promisify(s3.putObject.bind(s3))
    }
};

/**
 * ContentTypeとCacheControlを設定してS3のバケットへファイルを
 * アップロードする。
 * @async
 * @param {string} bucket バケット名
 * @param {string} key アップロード先のキー(バケット内のパス)
 * @param {string} pathname ローカルファイルのパス名
 * @returns {Promise<undefined>} アップロード完了で解決するPromise
 */
const uploadS3Bucket = async (bucket, key, pathname) => {
    try {
        const body = await fs.readFile(pathname);
        const contentType = mime.lookup(pathname);
        const params = {
            Body: contentType.match(/^text\//) ? body.toString() : body,
            Bucket: bucket,
            Key: key,
            CacheControl: "no-cache",
            ContentType: contentType,
        };
        console.log(`Uploading: ${pathname}`);
        console.log(`  [ContentType: ${params.ContentType}]`);
        console.log(`  ==> s3:${'//'}${params.Bucket}/${params.Key}`);
        await promised.s3.putObject(params);
    } catch(err) {
        console.warn(err.message);
    }
};

Content-Type の特定は npm mime-type が使えます

ファイル名(拡張子)からMIME Typeを得るために、 mime-types というnpmモジュールを使ってみました。別の npm mime-dbに依存しているのですが、どちらも週に1500万回ほどダウンロードされていますから実用上の問題はないでしょう。

Cache-Control も設定したい

Webサイトとして公開している場合、Cache-Controlを設定しておかないとブラウザキャッシュが有効になり、AWS側から制御しにくいので、ワタシはたいてい no-cache に設定してます。 CloudFrontのディストリビューションから公開している場合、24時間程度のキャッシュが効いていますので、S3から外へ出ていく容量(と課金)をあまり気にする必要はないはずです。 CloudFrontでInvalidationを作成してキャッシュを無効化しても、ブラウザキャッシュが効いているとページが更新されませんし。

参考:S3のオブジェクトのメタデータ

下表は S3 Bucket のファイルに設定可能なメタデータです。 メタデータ列は S3 のWEBコンソールで表示される名称で、パラメータキーはputObjectのパラメータで指定する場合のキー名称です。

メタデータ パラメータキー 詳細
Cache-Control CacheControl ブラウザキャッシュの指定 (☞MDN)
Content-Disposition ContentDisposition ファイルの扱い方法を指定 (☞MDN)
Content-Encoding ContentEncoding 圧縮アルゴリズムを指定 (☞MDN)
Content-Language ContentLanguage 閲覧者の言語を指定 (☞MDN)
Content-Type ContentType ファイルのMIME Typeを指定 (☞MDN)
Website-Redirect-Location WebsiteRedirectLocation リダイレクト先 (☞DevelopersIO)
x-amz-meta-<key> Metadata.<key> ユーザー定義メタデータ (☞AWS)
有効期限 Expires 削除される日時(☞AWS)

※ ユーザー定義メタデータ以外は、APIであらかじめ定義されているシステムメタデータです。

リンク