• Amazon S3: 파일 관리를 위한 aws-sdk 활용법 :: 마이구미
    AWS 2018. 6. 8. 17:38

    이 글은  아마존 웹 서비스(AWS) 중 S3 를 다뤄본다.

    주요 목적은 S3 를 통해 CMS, FTP 와 같은 리소스 관리 시스템을 구축에 필요한 aws-sdk 의 내부 함수들을 다뤄본다.

    자바스크립트를 기준으로 진행한다.

    자바스크립트를 사용하지않더라도 필요한 기능에 있어, 사용하는 함수의 종류 및 흐름을 익히는 것만으로 충분할 것이다. 

    aws-sdk - https://github.com/aws/aws-sdk-js


     AWS 중 S3 는 일반적으로 현재 기업 홈페이지와 같은 가벼운 사이트에 많이 사용한다.

    하지만 더 알아간다면, S3 의 활용사례는 무궁무진할정도로 정말 많은 기능이 존재한다.

    그 중 이 글에서는 리소스 관리와 같은 시스템을 구축하기 위한 알아두어야할 것들을 다뤄볼 것이다.

    예를 들어, 파일 업로드, 생성, 삭제 등과 같은 기능이라고 볼 수 있다.

    Github 관련 프로젝트 - https://github.com/hotehrud/cms-s3


    CMS


    우선 알아두고 가야하는 것이 있다.


    S3 는 어떤 스토리지 유형인가?


    S3 는 객체 스토리지이다.

    이것을 짚고 가는 이유는 의아하게 생각할 수 있는 것들이 존재하기 때문이다.

    예를 들어 이름 변경(rename) 과 같은 기능이다.


    sdk 내부 메소드에서 업로드는 upload(), 삭제는 deleteObject() 등과 같이 존재한다.

    그렇다면 이름을 변경에 관련된 rename 와 같은 메소드가 존재할 것이고, 사용하면 될 것 같다.

    하지만 그러한 메소드는 존재하지않는다.

    결과적으로 원본 파일을 복제하고 이름을 바꾼 파일을 생성한 후, 원본 파일 삭제하는 방식으로 이루어진다.


    이것이 바로 객체 스토리지라는 것을 이해해야한다는 것이다.

    S3는 단순히 일반적인 파일 시스템이 아닌 key-value 형태와 같은 객체 스토리지이다.

    객체의 key 값을 직접 변경할 수 없는 것처럼, 일반적인 접근이 아닌 다른 방식의 접근이 필요하다.




    크게 다음과 같은 목적을 위해 예제 코드를 통해 다뤄본다.

    자세한 사항은 공식 문서를 참고하길바란다. (https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html)


    • 버킷 모든 파일 가져오기
    • 파일 가져오기 (Binary 또는 URL)
    • 폴더, 파일 생성하기 (업로드)
    • 폴더, 파일 삭제하기


    1. 버킷내에 있는 모든 파일 가져오기


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    let lists = [];
    s3.listObjectsV2(
        {
            Bucket: "BUCKET_NAME"
        },
        (err, data) => {
            if (err) {
                throw err;
            }
            let contents = data.Contents;
            contents.forEach((content=> {
                lists.push(content.Key); // "ex) content.Key => assets/images/1.png"
            });
            console.log(lists);
        }
    );
    cs


    listObjectV2 메소드를 통해 버킷 내 모든 경로를 가져온다.

    위 예제에서는 Bucket 속성만 사용하지만, 더 많은 인자가 존재한다.

    예를 들어, Prefix 속성을 통해 특정 폴더의 모든 파일들을 가져올 수도 있다.

    또한, MaxKeys 속성을 통해 반환되는 최대 파일의 개수를 설정할 수 있다.


    파일이 너무 많아 한번에 못 가져올 경우나 나누어 가져와야할 필요가 있을 때가 존재한다.

    이러한 경우는 반환되는 값 중, IsTruncated 를 사용한다.

    관련 코드는 다음과 같다.


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    let lists = [];
    let params = {
        Bucket: "BUCKET_NAME";
    }
    function getList(params) {
        s3.listObjectsV2(params, (err, data) => {
            if (err) {
                throw err;
            } else {
                let contents = data.Contents;
                contents.forEach((content=> {
                    lists.push(content.Key);
                });
     
                if (data.IsTruncated) {
                    let obj = Object.assign({}, params, {
                        ContinuationToken: data.NextContinuationToken
                    });
                    getList(obj);
                }
            }
        });   
    }
    cs



    2. 파일 가져오기(stream)


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    s3.getObject(params, (err, data) => {
        if (err) {
            throw err;
        }
        // dataURL
        let dataURL = "data:image/jpeg;base64," + encode(data.Body);
     
        // blobURL
        const blob = new Blob([data.Body], {
            type: data.ContentType
        });
        const blobURL = URL.createObjectURL(blob);
    });
    cs


    getObject 메소드를 통해 데이터 스트림 형태로 가지고 온다.

    목적에 따라 원하는 형태로 변환하여 사용하면 된다.

    preview 또는 download 기능을 적용할 수 있다.



    2. 파일 가져오기(URL)


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    s3.getSignedUrl(
        "getObject",
        {
            Bucket: "test-vtouch",
            Key: "PATH" // ex) assets/
        },
        (err, url) => {
            if (err) {
                throw err;
            }
            console.log(url);
        }
    );
    cs


    getSignedURL 메소드를 통해 URL 형태를 반환한다.

    직접 접근할 수 있는 URL 을 가지고와서 다른 작업없이 바로 쓸 수 있는 형태이다.

    단순 preview 와 같은 목적에 적합하다.



    3. 파일 생성하기 (업로드)


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    s3.upload({
        Bucket: "BUCKET_NAME",
        Key: "KEY"// ex) assets/
        Body: stream // input.files[0]
    })
        .on("httpUploadProgress", evt => {
            // parseInt((evt.loaded * 100) / evt.total);
        })
        .send((err, data) => {
            console.log(data);
        });
    cs


    upload 메소드를 통해 새로운 파일을 생성할 수 있다.

    Key 값은 원하는 경로를 설정해주면 된다.

    Body 는 스트림 형태를 넘겨준다. ex) <input type="file"> 

    또한, httpUploadProgress 를 통해 로딩 현황을 실시간으로 보여줄 수 있다.

    * putObject 메소드를 통해서도 업로드 기능이 가능하다. 폴더 생성에서 사용할 것이다.



    4. 폴더 생성하기


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    s3.putObject({
        Bucket: "BUCKET_NAME",
        Key: "KEY" // ex) assets/js/
    }).send(err => {
        if (err, data) {
            throw err;
        } else {
            console.log(data);
        }
    });
    cs


    pubObject 메소드를 통해 새로운 폴더를 생성한다.

    중요한 건 Key 의 값이다.

    Key 값에 assets/js/ 를 설정했을 경우, assets 폴더 안에 js 라는 폴더가 생성된다.



    5. 파일 삭제하기


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    s3.deleteObject(
        {
            Bucket: "test-vtouch",
            Key: key
        },
        (err, data) => {
            if (err) {
                throw err;
            }
            console.log(data);
        }
    );    
    cs


    삭제의 경우 deleteObject 메소드를 사용한다.

    단순히 삭제를 원하는 경로를 지정해주면 된다.



    6. 폴더 삭제하기


    폴더 삭제의 경우에는 사전 작업이 필요하다.

    사전 작업으로는 빈 폴더를 유지해야한다는 것이다.

    즉, 폴더 안의 모든 파일을 삭제한 후, 폴더를 삭제 할 수 있다.


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    let params = {
        Bucket: "test-vtouch",
        Prefix: "KEY"
    };
    async function deleteFolder(params) {
        const listedObjects = await s3.listObjectsV2(params).promise();
        const deleteParams = {
            Bucket: params.Bucket,
            Delete: { Objects: [] }
        };
     
        listedObjects.Contents.forEach(({ Key }) => {
            deleteParams.Delete.Objects.push({ Key });
        });
     
        await s3.deleteObjects(deleteParams).promise();    
    }
     
    cs


    여러 파일 삭제를 위한 deleteObject 가 아닌 deleteObjects 메소드, 폴더 안의 파일을 찾기 위한 listObjectV2 메소드를 사용한다.


    여기서도 "1번 버킷 내 모든 파일 가져오기" 와 같이 isTruncated 처리가 필요할 수 있다.

    코드는 다음과 같다.


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    let params = {
        Bucket: "test-vtouch",
        Prefix: key
    };
    async deleteFolder(params) {
        const listedObjects = await s3.listObjectsV2(params).promise();
     
        if (listedObjects.Contents.length === 0) {
            return;
        }
     
        const deleteParams = {
            Bucket: params.Bucket,
            Delete: { Objects: [] }
        };
     
        listedObjects.Contents.forEach(({ Key }) => {
            deleteParams.Delete.Objects.push({ Key });
        });
     
        await s3.deleteObjects(deleteParams).promise();
     
        if (listedObjects.IsTruncated) {
            let obj = Object.assign({}, params, {
                ContinuationToken: listedObjects.NextContinuationToken
            });
            await this.deleteFolder(obj);
        } else {
            // callback
        }
    }
    cs


    댓글 6

    • 종무 2019.11.04 09:46

      안녕하세요. 좋은 글 잘 읽었습니다. await 할 때(line 6, 21)에 끝에 promise()를 마지막에 붙여주는데
      용도가 무엇인지 알 수 있을까요?
      블로거님의 콜백, promise 에 대한 다른 글도 읽어 봤는데 잘 모르겠습니다.

      • Favicon of https://mygumi.tistory.com 마이구미 mygumi 2019.11.04 20:24 신고

        await 를 하기 위해서는 메소드의 리턴값이 Promise 형태여야한다는 것을 인지하고 계실겁니다.
        단순히 .promise() 를 통해 리턴값을 Promise 형태로 해준 것입니다.
        이건 AWS SDK 에서 제공해주는 메소드입니다.
        아래를 참고하시면 도움이 되실거라 생각합니다!
        https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/using-promises.html

    • Favicon of https://kairoyang.tistory.com KairoYang 2020.03.25 12:17 신고

      마이구미님 안녕하세요
      글 잘 읽었습니다!
      제가 node와 ejs를 이용하여 www.unsplash.com 와 같은 사이트를 만들어 보고 있습니다.

    • Favicon of https://kairoyang.tistory.com KairoYang 2020.03.25 12:20 신고

      다운로드 기능을 하려면 어떻게 해야하는지 알려주실수 있으신가요?

    • Favicon of https://kairoyang.tistory.com KairoYang 2020.03.25 12:21 신고

      a태그에 download를 넣고 s3 url을 넣으면 해당 url로 이동합니다!

    • Favicon of https://kairoyang.tistory.com KairoYang 2020.03.25 12:22 신고

      한번에 댓글을 작성하면 티스토리가 제한하여 글을 작성하지 못하여 나눠서 작성하였습니다

Designed by Tistory.