【AWS S3とNode.jsを連携して画像をReactで表示させるまで】

AWS S3を利用して、直接Node.jsでfileをダウンロードしてみたのでその時のやり方を載せておく。

この記事では、IAMの利用から、S3にある画像をnode.jsで取得、Reactで表示できるようになる流れがわかってもらえればいいなという感じです。

また、今回はNode.jsのアプリとReactでのアプリでCORSを行うことを想定しているので、corsモジュールを使えるように設定をしていきます。
* * *
#####【AWS側の設定】
####１，S3にのみアクセスできるIAMグループを作る

今回は、AccessToS3OnlyGroup というグループをS3FullAccess権限のみをもたせて作る。

####２，新しくIAMユーザの作成
IAMユーザーを新しく作り、先程作ったAccessToS3OnlyGroupというグループに割り当てます。
ちなみに、AmazonS3FullAccessポリシーの中身はこのようになっています。

```jsx
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "s3:*",
      "Resource": "*"
    }
  ]
}
//Effect → Allow or Deny （許可 or 拒否）

//Action → どういった権限を与えるかなどの細かい設定 (GET, POST, など)

//Resouce → どのS3を使用しているのかといった設定

//Condition → このポリシーが適用される条件
```

####３，IAMユーザでログインする

・rootユーザーでログイン
・IAM→UsersからログインしたいIAMユーザーのsecurity credentials画面へ
・Summaryにあるサインインurlからログインする。
[https://~~~.signin.aws.amazon.com/console](https://udemyken3pei.signin.aws.amazon.com/console)（~~~の部分がアカウントエイリアスにあたる）
→アカウントIDは、IAMユーザでログイン後、MyAccountの右にある数字の羅列なので、ログイン時にこちらを入力してもログイン可能。

####４, 同画面でアクセスキーを作っておく
３番のときと同じsecurity credentials画面で、Access keys から「create access key」から、accessKeyId, secretAccessKeyを作成し、メモしておく。

####５, S3に画像をUploadする

* * *
【以降はNode.js側の設定になります】
####６，app.jsでS3と連携するために必要なモジュールをinstal

`npm install cors`
`npm install aws-sdk`
corsのoptionsについて
・origin ... Access-Control-Allow-Origin CORSヘッダーを設定する。どのオリジンにヘッダーを付与するか、しないかなどの設定ができる。
・credentials ... 資格情報系の許可（Cookieなど）を与えるかどうかなどの設定ができる。
今は、cookieを使わないのでfalseにしておく。

```jsx
const express = require('express')
const app = express();
const cors = require('cors')
const AWS = require('aws-sdk')

app.use(cors({ origin: true, credentials: false }))
```

ちなみに、`origin : false` にすると以下のようなエラーが出る。

```jsx
Access to XMLHttpRequest at 'http://localhost:8080/menu' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
```

####７, S3からファイルを取得
・S3から特定のバケット内の特定のファイルを取得する。
/imageというURLでPOSTされたら、画像の取得処理が走る。

```jsx

app.post('/image', (req, res) => {
    const params = {
        'Bucket': 'My-app-imgs',
        'Key': 'A.jpg'
    }
    s3Client.getObject(params, (err, data) => {
         if(err){
            console.log(err)
         }else{
            console.log(data)
　　　　　　　res.send(data)
         }
    })
})
```


・上記のコードを実装するときに出たエラー（気をつけましょう）
→Keyと指定したつもりだったが、keyになっており、一文字目が大文字になってなかった。

```jsx
MultipleValidationErrors: There were 2 validation errors:
* MissingRequiredParameter: Missing required key 'Key' in params
* UnexpectedParameter: Unexpected key 'key' found in params
    at ParamValidator.validate
```

・S3の特定のバケットからファイル一覧を取得する方法

```jsx
app.post('/image', (req, res) => {
    const params = {
        'Bucket': 'My-app-imgs'
    }
    s3Client.listObjects(params, (err, datas) => {
        if(err){
            console.log(err)
        }else{
            console.log(datas)
            res.send(datas)
        }
    })
})
```

・dataの中身
Bodyにバイナリで返されるのでstringに変換する必要がある。

```jsx
{
  AcceptRanges: 'bytes',
  LastModified: 2021-03-28T07:55:49.000Z,
  ContentLength: 867920,
  ETag: '"5b6a5a0c6aea626483f3bfbda1c32da3"',
  ContentType: 'image/jpeg',
  Metadata: {},
  Body: <Buffer ff d8 ff e0 00 10 4a 46 49 46 00 01 01 00 00 01 00 01 00 00 ff db 00 84 00 05 05 05 05 06 05 06 07 07 06 09 09 08 09 09 0d 0c 0b 0b 0c 0d 13 0e 0f 0e ... 867870 more bytes>
}
```

* * *
####８, React側で受けとって表示
Buttonタグをクリックすると、axiosでpostされる。
Bufferで受けとったimageを描画するには、コツがある。


```jsx
import React from 'react'
import ReactDOM from 'react-dom'
import axios from 'axios';

class Image extends React.Component {

    constructor(props){
        super(props)
        this.state = {
            data: ''
        }
        this.getImage = this.getImage.bind(this)
    }

    getImage(){
        const url = 'http://localhost:8080/image'
        axios.post(url).then((res) => {
            this.setState({ data: res.data })
        })
    }

    render(){
        return (
            <div>
                <p>Imageを描画</p>
                <button onClick={this.getImage}>get Image</button>
                <img src={`data:image/jpeg;base64,${this.state.data}`} style={{ width: '300px' }}/>
            </div>
        )
    }
}

export default Image
```

・streamを使ったほうがいいっぽいが、今回はシンプルさ重視、流れ重視で書きました。