Laravelアプリのファイルアップロードからアップした画像ファイルの配信サーバーとしてAWSのS3を利用する場合、いくつか留意事項がある。
s3バケット以下へのアクセス許可
デフォルトだと、バケット以下のファイルはpublicではないので、バケットのプロパティ設定の「アクセス許可」「バケットポリシーの編集」を設定する
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AddPerm",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::[バケット名]/*"
}
]
}
格納画像ファイルのMimeType指定
Laravel側でアップロードした画像をS3に格納するには、config/filesystems.phpでs3の設定をした上で、Controllerでこんな感じ。
public function store($request)
{
...
if ($request->hasFile('photo'))
$stored_path = $request->photo->store('photos', 's3');
...
}
s3で公開されるURLは、Storage::url($stored_path)で得られる。
HTMLのimgタグのsrcに指定すると問題なく表示されるが、そのURLにブラウザから直接アクセスすると、MimeTypeが指定されていないので、デフォルトの「octet-stream」扱いになって、ダウンロードされてしまう。
これを回避するには同じくS3側のファイルのプロパティから、メタデータとして適切なMimeType設定をすればいいが、S3へのファイル保存時にアプリでやるには、Laravel5.4〜の場合、上のstoreメソッドをこのように書く。
$options = [
'visibility' => 'public',
'mimetype' => 'image/jpeg'
];
if ($request->hasFile('photo'))
$stored_path = $request->photo->store('photos', 's3', $options);
storeメソッドの第3引数が、filesystem Adapter(この場合s3)へ渡すオプションとなる。ちなみに visibilityは上で書いた公開設定をpublicにするという設定。
(参考)
https://laravel.com/api/5.4/Illuminate/Http/UploadedFile.html#method_store
5.3だとこれができないので困る。てか、困った方々による議論の末に、上のstoreオプションが5.4に取り込まれたよう。その経緯はこちら。
(参考)
https://github.com/laravel/framework/pull/16416
5.3で同じことをするには、UploadedFileのstoreメソッドと、そこから呼ばれるfilesystemのAdapterクラスの書き込みメソッドを自前でoverrideするような形になる。
public function store($requset)
{
...
if ($request->hasFile('photo'))
$stored_path = $this->_storeUploadFile($request->photo);
...
}
private function _storeUploadFile($uploadFile)
{
$driver = Storage::disk('s3')->getDriver();
$mimeType = $uploadFile->getClientMimeType();
$stream = fopen($uploadFile->getRealPath(), 'r+');
$path = trim('photos/'. $uploadFile->hashName(), '/');
$result = $driver->putStream($path, $stream, [
'visibility' => 'public',
'mimetype' => $mimeType
]);
if (is_resource($stream)) fclose($stream);
return $result ? $path : false;
}
MimeTypeの判定は UploadFileの親クラスがgetClientMimeTypeというメソッドを持ってるようなんでそれを利用。精度は知らない。