[AWS]AWS 맛보기 #2 (S3)

PEPPERMINT100·2020년 12월 4일
0

서론

전 글에서 AWS를 어떤식으로 사용할지에 대한 큰 틀을 잡아보았습니다. 이번엔 직접 자바스크립트로 어플리케이션을 만들며 AWS에 대해 알아보도록 하겠습니다.

버킷 만들기

먼저 aws 홈페이지에 접속하고 로그인을 하면 aws 대시보드에 접속할 수 있습니다.

여기에 s3를 검색하고 s3 대시보드에 접속합니다.

버킷만들기 버튼을 누르고

버킷이름만 잘 정하고 리전을 서울로 해줍니다. 결제부터 모든 AWS 인스턴스는 리전 별로 관리가 된다고 하니 어떤 리전을 사용했는지는 잘 알아두어야 합니다.
나머지는 디폴트 세팅으로 가되 권한 설정에서 퍼블릭 액세스 차단에 체크가 되어있는지 확인합니다. 여기서 가장 상단의 모든 액세스 차단의 체크를 풀어줍니다. 이 후에 이 세팅을 다시 진행할 것입니다.
그리고 이제 AWS에서 가장 중요하게 여겨야할 키를 발급 받아야 합니다. 키는 자신의 AWS 인스턴스에 접속하기 위한 비밀번호와 비슷한 개념으로 절대로 노출되어서는 안됩니다.
오른쪽 상단 자신의 아이디를 클릭하고 내 보안 자격 증명 탭으로 이동하면 중간에 액세스 키를 발급 받을 수 있습니다. 새 액세스 키 만들기를 누르면 ID 키과 액세스 키를 줍니다. 이 키를 메모장에 복사해 두도록 합니다.
아주 간단하게 S3 버킷을 생성하였고 이제 이미지 파일을 이곳에 저장하는 어플리케이션을 만들어보도록 하겠습니다.

더 진행하기 전에 이 글을 읽고 react와 express로 이미지 파일을 업로드하는 과정을 배웁니다.

React Part

CRA든 Webpack이든 기본적인 리액트 세팅을 합니다. AWS를 다루면서 중요한 부분은 env 세팅입니다. 먼저 env 세팅을 해줍니다.
만약 CRA(create-react-app)을 통해 리액트 앱을 생성했다면

yarn add dotenv

모듈을 통하여 변수 세팅을 할 수 있습니다.
Webpack을 통하여 세팅을 했다면

yarn add dotenv-webpack

을 통하여 세팅을 하고 .env에는

ENDPOINT=http://localhost:5000
//.env

이렇게 서버의 엔드포인트(저는 5000번 포트로 했습니다.)를 정해줍니다. 만약 git에 올릴 거라면 .gitignore를 통해 꼭 커밋 목록에서 제외해줍니다.
웹팩 세팅은 여기에서 간단하게 세팅 할 수 있습니다.

다음은 제 리액트 코드의 폴더 구조입니다.

대략적인 이해를 위해 코드 구조를 설명해드렸습니다. 모든 코드는 깃허브에 올릴 예정이니 걱정하지 마세요.
저는 config 폴더에 env.js 파일을 만들고


export default {
    ENDPOINT:process.env.ENDPOINT 
}
// env.js

위와 같이 자바스크립트 오브젝트로 만들어놓고 사용을 합니다. 그리고 App.js는 간단하게

import React from "react";
import "./App.css";
import UploadForm from "./components/UploadForm";
import ImageList from "./components/ImageList";
function App() {
  return (
    <div className="app">
      <UploadForm />
      <ImageList />
    </div>
  );
}
export default App;
// .App.js

이미지와 글귀를 업로드할 폼과 업로드된 이미지와 글귀들을 보여줄 List 컴포넌트를 작성하고

import React, { useState } from 'react'
import env from "./../config/env"
import axios from "axios"
import regeneratorRuntime from "regenerator-runtime"
const ENDPOINT = env.ENDPOINT
const containerStyle = {
    position: "relative",
    marginTop: "100px",
    height: "20vh"
}
const formStyle = {
        display: "flex",
    flexDirection: "column",
    justifyContent: "space-between",
    alignItems: "center",
    height: "200px"
}
const fileInputStyle = {
    display: "none"
}
const textInputStyle = {
    all: "unset",
    width: "500px",
    height: "30px",
    border: "1.5px solid #bdc3c7",
}
const labelStyle ={
    all:"unset",
    display: "block",
    cursor: "pointer",
    fontSize: "24px",
    color: "#555555",
}
const buttonStyle ={
    all: "unset",
    display: "block",
    width: "120px",
    textAlign: "center",
    padding: "12px 18px",
    cursor: "pointer",
    backgroundColor: "#2980b9",
    borderRadius: "5px",
    fontSize: "16px",
    fontWeight: "bold",
    color: "#fff"
}
const UploadForm = () => {
    const [file, setFile] = useState(undefined)
    const [text, setText] = useState("")
const onSubmit = async (e) => {
        e.preventDefault()
        if(!file) return alert('Please select any image file')
        const formData = new FormData()
        formData.append("img", file)
        formData.append("text", text)
        try{
            const res = await axios.post(`${ENDPOINT}upload`, formData)
            console.log(res)
            
        }catch(err){
            if(err) console.error(err)
        }
            setText("")
    }
const onSelectFile = (e) => {
        setFile(e.target.files[0])
    }
const onChangeText = (e) => {
        setText(e.target.value)
    }
return (
        <div style={containerStyle}>
            <form onSubmit={onSubmit} style={formStyle}>
                <label htmlFor="upload" style={labelStyle}>🎈 Select File</label>
                <input onChange={onSelectFile} type="file" id="upload" style={fileInputStyle}/>
                <section>
                { file ? (
                    <p>{file.name}</p>
                ) : <p>No file selected</p> }
                </section>
                <input onChange={onChangeText} type="text" style={textInputStyle} placeholder="How's it going?" value={text} />
                <button style={buttonStyle} type="submit"> Upload </button>
            </form>
        </div>
    )
}
export default UploadForm
// UploadForm.js

업로드 폼을 작성합니다. 업로드 방식은 제가 여기서 사용한 방식과 같습니다. 간단하게 설명하자면

<input onChange={onChange} type="file" id="upload" style={inputStyle}/>

인풋에 올라온 파일을 onChange를 통해 감지하고

const [file, setFile] = useState(null)

file이라는 state에 넣어준다음

const onSubmit = async (e) => {
        e.preventDefault()
        if(!file) return alert('Please select any image file')
        const formData = new FormData()
        formData.append("img", file)
        formData.append("text", text)
        try{
            const res = await axios.post(`${ENDPOINT}upload`, formData)
            console.log(res)
            
        }catch(err){
            if(err) console.error(err)
        }
            setText("")
    }

그 파일을 formData에 넣어서 axios를 통해 서버로 보내줍니다. fetch를 사용해도 상관없습니다. 다만 저기서 formData의 이름을 “img”로 해두었다는 것을 잊지마세요.

NodeJS Part

이제 리액트로부터 받은 formData를 S3 버킷에 넣어보도록 하겠습니다. 저희는 multer라는 라이브러리를 사용할 예정인데요. 위에서 multer 사용법에 관련된 글을 올렸으니 필요하시면 참고하시는게 좋습니다.
먼저

yarn init -y

을 통해 package.json을 만들고 server.js 파일을 메인으로 만들어줍니다.

위는 제 서버 프로젝트 폴더의 구조입니다. 역시 모든 코드는 깃허브에 올리도록 하겠습니다.
이제 필요 라이브러리 들을 설치해줍니다.

yarn add express multer-s3 dotenv cors aws-sdk multer

그리고 가장 먼저 역시 dotenv를 통해 환경 변수 설정을 해주고 이 안에 리액트 서버 포트 주소와 위에서 발급받은 키 ID와 액세스 키도 env 변수로 추가해줍니다.
그리고 아래와 같이 server.js를 작성합니다.

import express from "express"
import uploadRoute from "./routes/uploadRoute"
import cors from "cors"
import env from "./config/env"
const app = express()
const PORT = process.env.PORT || 5000
const CLIENT = process.env.CLIENT || "http://localhost:8080"
app.use(express.json())
app.use(express.urlencoded({ extended: false}))
app.use(cors({
    origin: CLIENT
}))
//routes
app.use('/api/upload', uploadRoute)
app.listen(PORT, () => {
    console.log(`server running on PORT ${PORT}`)
})
// server.js
그리고 routes 라는 이름으로 폴더를 만들고 라우트를 파일을 만들어줍니다.
import express from "express"
import s3Upload from "./../service/s3-upload"
import env from "./../config/env"
const router = express.Router()
const s3ImageUpload = s3Upload.single('img')
router.post('/', (req, res) => {
        s3ImageUpload(req, res, (err) => {
        if(err){
            return res.send({ err })
        }
        const text = req.body.text
        const imgLocation = req.file.location
        const imgKey = req.file.key
return res.json({
            msg: "ok",
            body: req.body
        })
    })
})
export default router
// UploadRoute.js

여기서 s3ImageUpload라는 미들웨어 함수를 만들어서 요청이 들어오면 업로드를 하고 그 이미지 정보를 다시 클라이언트에 돌려주도록 합니다.

const text = req.body.text
const imgLocation = req.file.location
const imgKey = req.file.key
그리고 저희는 이 부분을 데이터베이스에 저장하여 적절한 이미지 리스트를 만들어낼 것입니다.
import multer from "multer"
import multerS3 from "multer-s3"
import aws from "aws-sdk"
import env from "./../config/env"
aws.config.update({
  secretAccessKey: env.AWS_SECRET_ACCESS_KEY,
  accessKeyId: env.AWS_ACCESS_ID,
  region: env.AWS_REGION 
})
 
const s3 = new aws.S3()
const fileFilter = (req, file, cb) => {
    if(file.mimetype === 'image/jpeg' || file.mimetype === 'image/png'){
        cb(null, true)
    }else{
        cb(new Error('Invalid File Type'), false)
    }
}
const storage = multerS3({
    s3,
    bucket: env.AWS_S3_BUCKET_NAME,
    acl: 'public-read',
    metadata: function (req, file, cb) {
      cb(null, {fieldName: "test"});
    },
    key: function (req, file, cb) {
      cb(null, file.fileName + Date.now().toString())
    }
})
const s3Upload = multer({
    fileFilter,
    storage,
    limits: { fileSize: 1000000 }
})
export default s3Upload
// s3-upload.js

가장 중요한 부분입니다. 먼저

aws.config.update({
  secretAccessKey: env.AWS_SECRET_ACCESS_KEY,
  accessKeyId: env.AWS_ACCESS_ID,
  region: env.AWS_REGION 
})

처음에 받은 액세스키와 아이디를 지정해줍니다. 만약 로컬에서만 간단하게 돌릴 거라면 그냥 키를 발급 받은대로 넣어주셔도 됩니다만 저는 깃허브에 올릴 예정이기 때문에 키를 전부 env 세팅에 넣어서 처리해주었습니다. region은 s3 대시보드에서 url을 확인하면 region이라는 파라미터로 확인할 수 있습니다. 보통 한국에서 진행중이라면 ap-northeast-~라는 식으로 되어있습니다.

const s3 = new aws.S3()

먼저 aws s3 객체를 만들어주고

const storage = multerS3({
    s3,
    bucket: env.AWS_S3_BUCKET_NAME,
    acl: 'public-read',
    metadata: function (req, file, cb) {
      cb(null, {fieldName: file.fieldname});
    },
    key: function (req, file, cb) {
      cb(null, file.fileName + Date.now().toString())
    }
})

그리고 위와 같이 storage를 만들어서 넣어줍니다.
bucket에는 처음에 버킷을 만들때 설정했던 버킷이름을 넣어주고 acl을 접근 권한 관련입니다. 저희는 누구든 접근가능하게 일단 public-read로 해놓고 키와 메타데이터는 aws에서 파일관리하기 위한 식별자로 일단 key만 파일 이름과 현재 시간으로 저장할 수 있도록 해둡니다.

const fileFilter = (req, file, cb) => {
    if(file.mimetype === 'image/jpeg' || file.mimetype === 'image/png'){
        cb(null, true)
    }else{
        cb(new Error('Invalid File Type'), false)
    }
}

파일 필터는 이제 파일이 들어오기 전에 파일 형식을 확인해줍니다. 저희는 이미지 파일만 받을 것이므로

if(file.mimetype === 'image/jpeg' || file.mimetype === 'image/png'

위와 같은 조건을 사용하였습니다. 정규 표현식에 익숙하다면 정규 표현식을 사용하여 걸러내는 것도 좋습니다.
그리고 지금까지 만들었던 파일 필터와 storage를

const s3Upload = multer({
    fileFilter,
    storage,
    limits: { fileSize: 1000000 }
})

multer에 넣어줍니다. 그리고 리액트앱에서 파일을 선택하고 submit 버튼을 누르면 s3 버킷에 이미지 파일이 들어오는 것을 확인 할 수 있습니다.
다음에는 요청에서 받은 text와 이미지 경로를 RDS를 통해 데이터베이스에 넣는 부분을 진행해 보도록 하겠습니다. 코드는 여기에서 확인할 수 있습니다.

profile
기억하기 위해 혹은 잊어버리기 위해 글을 씁니다.

0개의 댓글