📚

【AWS】S3から画像、動画を取得して表示する【Node.js】

2021/03/07に公開

背景

S3のポリシーを公開することができず、画像、動画取得する必要があった。
getObject()を使用して、バイナリを取得する方法まではあるものの、そこから先にがなくて非常に苦労しました。
そのため、誰かの役に立てればと思い、備忘録を兼ねて記事にしました。
あくまで自己流なため、正しい保証はできません。
的はずれだったり間違った情報や記載があればご指摘頂けると幸いです。

※このやり方はサーバー経由で画像、動画取得するため、サーバーに負荷がかかります。
 S3のURL公開、期限付きURLの発行で対応できる場合は、そのやり方をおすすめします。

環境

フロントエンド:angular(11.2.1)
バックエンド:Node.js(14.9.0)

ソース自体は複雑で難しい内容ではありません。
そのため、他のフロントエンド、バックエンドでも実装は比較的簡単かと思います。

流れ

①angularからNode.jsへリクエストを飛ばす。
②Node.jsからS3にGetObject()でS3の画像、動画を取得して、blob形式でフロントに渡す。
③angularで受け取ったblob形式の画像、動画をcreateObjectURL()を使用してURLを生成する。
④生成したURLをHTMLへ渡し、画像を表示する。
※生成したURLはあくまでブラウザのキャッシュに保存してある画像や動画をURL化しているだけです。なので、流出しても使用はできません。またブラウザを一度閉じてキャッシュを消したりしても使えなくなります。

①angularからNode.jsへリクエストを飛ばす。

まずはHTMLで適当にボタンを付けてonclick()をします。

get-object.component.html
<button (click)="getPhoto()">写真取得</button>
<img *ngIf="photoSrc"[src]="photoSrc" alt="写真URL">

次にonclick()でangularへリクエストを飛ばします。
responseTypeをblobにしておくと、blob変換する手間が省けます。

get-object.component.ts
  private url = 'http://localhost:3000/api/photo';
  public photoSrc:any = null;
  
    constructor(
    private http: HttpClient,
    private sanitizer:DomSanitizer
  ) {
    
  }
  ngOnInit(): void {
  }

getPhoto() {
    //http.getをするためのオブジェクトを生成
    this.http.get(this.url, { responseType: 'blob' })
      .subscribe(response => {        
        // 成功時の処理
        const blobPhotoData = response;
	// まずはblobで写真または動画取得できているか確認
	console.log(blobPhotoData);
    },error=>{
   //失敗時の処理
     console.log(error); 
    });
  }

もちろんサーバー側(Node.js)を設定しないと、画像取得できないので、サーバー側を書きます。

②Node.jsからS3にGetObject()でS3の画像、動画を取得して、blob形式でフロントに渡す。

app.jsを行き先を設定します。

app.js
app.use('/api/photo', photoRouter);

photo.jsでS3から写真を取得して、responseで返します。
アクセスキーやシークレットキーなどを適宜入力して、必須項目をうめてください。

photo.js
var express = require('express');
var router = express.Router();
const AWS = require('aws-sdk');

/* GET users listing. */
router.get('/', async (req, res, next) => {

  const s3 = new AWS.S3({
  accessKeyId: 'XXXXXXXXXXXXXXXXXXXXXXX',
  secretAccessKey: 'XXXXXXXXXXXXXXXXXXXXXX',
  region: 'リージョン名',
  // 日本であればリージョン名は基本的に「ap-northeast-1」だと思います。
})

 const params = {
  Bucket: 'バケット名', 
  Key: '取得したいS3に保存しているファイル名', 
 };
 
 const photoResult = await s3.getObject(params).promise();
 // Body以外を返す場合は、①で設定したResponseType:'blob'を外す必要があります。
  res.send(photoResult.Body);
});

module.exports = router;

③angularで受け取ったblob形式の画像、動画をcreateObjectURL()を使用してURLを生成する。

①で作成したget-object.component.tsのgetPhoto()関数にURLを生成する処理を付け加えていきます。

get-object.component.ts
  getPhoto() {
    //http.getをするためのオブジェクトを生成
    this.http.get(this.url, { responseType: 'blob' })
      .subscribe(response => {        
        // 成功時の処理
        const blobPhotoData = response;
        // 取得したblobをURLに変更
        const photoUrl = URL.createObjectURL(blobPhotoData);
	
        // angularはURLを直接HTMLへ渡すと、セキュリティでブロックされるので、URLを信頼済みにマークします。
	// angular用ですので、他のフロントエンドの場合は不要または変更が必要です。
        const sanitizedUrl = this.sanitizer.bypassSecurityTrustUrl(photoUrl);

	// HTMLへ渡すため、photoSrcへ代入します。
        this.photoSrc = sanitizedUrl;
        
    },error=>{
   //失敗時の処理
     console.log(error); 
    });
  }

これで準備が整いました。

④生成したURLをHTMLへ渡し、画像を表示する。

早速、nodeを起動して、表示させてみます。
写真取得のボタンを押すと、、、

S3に保存している画像が表示されました!
もちろん、開発者ツールで画像URLをコピーして他の端末や別ブラウザで入力しても表示はされません!

終わりに

以上がS3のポリシーを公開せずに画像を取得する方法でした。
今回は結構特殊な事例だったので、このような対処をやりました。
実際はURL公開や期限付きURLの発行で対処できるのがほとんどだと思います。
サーバーに負荷がかかっても良いのであれば、今回のやり方も選択肢の一つとして扱ってみてください。
最後までご覧いただきありがとうございました。

Discussion