[ErrorCatcher 3] AWS S3 file upload & Object

LILO Ghim·2021년 12월 20일
0

S3

"사용자가 web service에서 file을 업로드 해야 할 때 back-end는 파일을 database에 어떻게 저장하죠?"


라는 질문으로부터 시작된 것이 S3의 시작이었다

(정확히 1주일 전 2차 프로젝트의 시작이 S3였다,,, 난 이제 겨우 RDS 한 번 해보고, aws 배포 한 번 해본 미약한 자인데,,,)

(하 나 이거 한 번 읊고 확인 받고 싶었는데,,, 그림선생님이 안보인다,,,)

결론은!

1) front-end로 부터 파일을 list 형태로 받고,
2) aws의 s3 bucket에 파일을 업로드 하면,
3) s3가 url을 만들어주고
4) 어.짜.피 url의 형식은 정해져있으니
5) 그 url을 database에 저장하기만 하면 된다


aws 세팅하는 과정이나, boto3를 사용하는 방법은 많으니, ErrorCatcher로써 고민했던 부분들을 남기기로!

문. 제. 해. 결. 프. 로. 세. 스.

1. 등록된 user(여기서는 user이자 host로 등록되어 host_id가 있는)를 어떻게 받을 것인지?

1) query parameter로 받는다? `request.GET.get('host_id)`
2) path parameter로 받는다? `def post(self, request, 'host_id')`
3) json body에 받는다? `data=json.loads(request.body)`

결국, front-end에서 파일을 form-data로 받을 것이기 때문에, form-data에는 파일 뿐만이 아니라 뭐든지 담아 보내고 받을 수 있기 때문에, host_id까지 함께 담아 보내면 된다!

how?

그럼 form-data에 담아오는 값을 무슨 수로 빼지?

이제부터 찍는다!

print(request.POST)
>>> <QueryDict: {'host_id': ['22']}>

일단 여기까지는 그냥 찍어본건데 뭐가 있긴 있다!

host_id = request.POST.get('host_id')
print(host_id)
>>> 22

host_id = request.POST['host_id']
print(type(host_id))
<class 'str'>

결국 해답은 POST 였다!!!
.get이나, .GET.get이나 쓸 줄 알았지,,,,,
다음!

2. 같은 이름의 파일을 업로드 하면 overwriting인데, 어떻게 구분할 것인가?
파일명을 고유값으로 변경하여 업로드한다???

how?
uuid를 사용해서 랜덤값으로 이름을 변경

file._set_name(str(uuid.uuid4()))

3. Object에 대한 접근!

>>> import boto3
>>> s3      = boto3.resource('s3')
>>> s3
s3.ServiceResource()
>>> s3.Bucket(AWS_STORAGE_BUCKET_NAME).put_object(Key = f'{file}', Body = file)

Bucket,,, put_object,,, 오케이,,,,

Bucket.__dir__()을 찍어볼까?

>>> s3.Bucket
<bound method ResourceFactory._create_class_partial.<locals>.create_resource of s3.ServiceResource()>
>>> s3.Bucket('ghimlilo')
s3.Bucket(name='ghimlilo')

Bucket에 'ghimlilo'를 넣었더니 bucket이름이 'ghimlilo'가 되네?
그럼 .__dir__()을 찍어보자?

>>> s3.Bucket('ghimlilo').__dir__()
['meta', '_name', 'name', 'create', 'delete', 'delete_objects', 'put_object', 'creation_date', 'multipart_uploads', 'object_versions', 'objects', 'Acl', 'Cors', 'Lifecycle', 'LifecycleConfiguration', 'Logging', 'Notification', 'Object', 'Policy', 'RequestPayment', 'Tagging', 'Versioning', 'Website', 'get_available_subresources', 'wait_until_exists', 'wait_until_not_exists', 'load', 'upload_file', 'download_file', 'copy', 'upload_fileobj', 'download_fileobj', '__module__', '__doc__', '__init__', '__repr__', '__eq__', '__hash__', '__dict__', '__weakref__', '__str__', '__getattribute__', '__setattr__', '__delattr__', '__lt__', '__le__', '__ne__', '__gt__', '__ge__', '__new__', '__reduce_ex__', '__reduce__', '__subclasshook__', '__init_subclass__', '__format__', '__sizeof__', '__dir__', '__class__']

put_object가 있고???

Object 발견!!!
그럼, Object를 찍어?

>>> s3.Bucket('ghimlilo').Object
<bound method ResourceFactory._create_class_partial.<locals>.create_resource of s3.Bucket(name='ghimlilo')>
#<bound method ResourceFactory._create_class_partial.<locals>.create_resource of s3.ServiceResource()>
#s3.Bucket에 이름이 들어간다!

>>> s3.Bucket('ghimlilo').Object.__dir__()
['__repr__', '__hash__', '__call__', '__getattribute__', '__setattr__', '__delattr__', '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', '__get__', '__new__', '__reduce__', '__func__', '__self__', '__doc__', '__str__', '__init__', '__reduce_ex__', '__subclasshook__', '__init_subclass__', '__format__', '__sizeof__', '__dir__', '__class__']

누가봐도 class네?(왜냐면,,, 대문자라서,,,,,?)
그럼 아무거나 넣어볼까?(여기서부터는 내 뇌에서 나오기 힘든 부분,,,)
그럼 Object에 'key'를 넣어봐?

>>> s3.Bucket('ghimlilo').Object('key')
s3.Object(bucket_name='ghimlilo', key='key')

key? 그 key? 파일이름?


>>> s3.Bucket('ghimlilo').Object('key').__dir__()
['meta', '_bucket_name', '_key', 'bucket_name', 'key', 'load', 'reload', 'copy_from', 'delete', 'get', 'initiate_multipart_upload', 'put', 'restore_object', 'delete_marker', 'accept_ranges', 'expiration', 'restore', 'archive_status', 'last_modified', 'content_length', 'e_tag', 'missing_meta', 'version_id', 'cache_control', 'content_disposition', 'content_encoding', 'content_language', 'content_type', 'expires', 'website_redirect_location', 'server_side_encryption', 'metadata', 'sse_customer_algorithm', 'sse_customer_key_md5', 'ssekms_key_id', 'bucket_key_enabled', 'storage_class', 'request_charged', 'replication_status', 'parts_count', 'object_lock_mode', 'object_lock_retain_until_date', 'object_lock_legal_hold_status', 'Acl', 'Bucket', 'MultipartUpload', 'Version', 'get_available_subresources', 'wait_until_exists', 'wait_until_not_exists', 'upload_file', 'download_file', 'copy', 'upload_fileobj', 'download_fileobj', '__module__', '__doc__', '__init__', '__repr__', '__eq__', '__hash__', '__dict__', '__weakref__', '__str__', '__getattribute__', '__setattr__', '__delattr__', '__lt__', '__le__', '__ne__', '__gt__', '__ge__', '__new__', '__reduce_ex__', '__reduce__', '__subclasshook__', '__init_subclass__', '__format__', '__sizeof__', '__dir__', '__class__']

누가봐도 붙여서 찍어보고 싶은 것 하나???

e_tag


>>> s3.Bucket('ghimlilo').Object('key').e_tag
error djkafljdskfajld;kas;jfdjkfl;jkdl;jafkld;lasjfl;

당연히 에러가 나겠지? 'key'가 없으니까,,, 자 그럼 key는 헷갈리니까 아무거나 다시 찍어봐?
>>> s3.Bucket('ghimlilo').Object('abc')
s3.Object(bucket_name='ghimlilo', key='abc')

응??? key='abc'???
>>> s3.Bucket('ghimlilo').Object('abc').__dir__()
['meta', '_bucket_name', '_key', 'bucket_name', 'key', 'load', 'reload', 'copy_from', 'delete', 'get', 'initiate_multipart_upload', 'put', 'restore_object', 'delete_marker', 'accept_ranges', 'expiration', 'restore', 'archive_status', 'last_modified', 'content_length', 'e_tag', 'missing_meta', 'version_id', 'cache_control', 'content_disposition', 'content_encoding', 'content_language', 'content_type', 'expires', 'website_redirect_location', 'server_side_encryption', 'metadata', 'sse_customer_algorithm', 'sse_customer_key_md5', 'ssekms_key_id', 'bucket_key_enabled', 'storage_class', 'request_charged', 'replication_status', 'parts_count', 'object_lock_mode', 'object_lock_retain_until_date', 'object_lock_legal_hold_status', 'Acl', 'Bucket', 'MultipartUpload', 'Version', 'get_available_subresources', 'wait_until_exists', 'wait_until_not_exists', 'upload_file', 'download_file', 'copy', 'upload_fileobj', 'download_fileobj', '__module__', '__doc__', '__init__', '__repr__', '__eq__', '__hash__', '__dict__', '__weakref__', '__str__', '__getattribute__', '__setattr__', '__delattr__', '__lt__', '__le__', '__ne__', '__gt__', '__ge__', '__new__', '__reduce_ex__', '__reduce__', '__subclasshook__', '__init_subclass__', '__format__', '__sizeof__', '__dir__', '__class__']

>>> s3.Bucket('ghimlilo').Object('abc').e_tag
error djkafljdskfajld;kas;jfdlasjfl;

또 당연히 에러가 나겠지? 'abc'가 없으니까,,, 실제 내가 Bucket에 올렸던 파일명으로 다시 위 과정을 반복해본다면???
>>> s3.Bucket('ghimlilo').Object('api.png').e_tag
'"c3cba12848ec31261d08532e3e88c3de"'

ㅘ!!!

엔터티태그???


이렇게 Object에 얼마든지 접근이 가능하다!!!

그래서 왜???

Object에 대한 접근의 출발은
과연 이미지를 업로드하는 사용자마다 과연 폴더를 만들어 주는 것이 '관리'적인 차원에서 필요한 것인가?에 대한 의문이었다.

해당하는 API에서 S3 Bucket에 이미지를 업로드하는 이유는 업로드하면서 만들어주는 url을 db에 저장만 하면 되기 위함이고, modeling을 할 때, 이미 image table에 host_id가 foreign key로 연결되어 있으니 굳이 폴더생성은 필요 없다.

만약 object의 수정 및 삭제 등의 접근이 필요하다면 이렇게 가능하다는 것을
결국, request.user 대제전(https://velog.io/@kimlilo/Django-Decoratorsignin) 때처럼 한. 줄. 한. 줄. 찍. 어. 보. 면 된다!

HJ님께서 이미 1주일 전에 찍기를 말씀하셨으나, 찍다 포기한 나,,, 결국 이렇게 돌아오지!

이 컴퓨터 천재들,,, 너무 멋져,,,

이것이 바로 문.제.해.결.능.력.

그리고 나의 의식의 흐름이 담긴 날 것 그대로의 코오드으


class ImageUploadView(View):
    def post(self, request):

            #1. 파일을 리스트로 받음 : how? body?
            #2. s3 사용 : boto3.resource('s3')
            #3. host_id : path parameter ???
            #4. data = open('file', 'rb') 
            #5. aws 업로드 : put_object
            #6. url 생성
            #7. db 저장
            #8. error : 같은 이름 파일은? 랜덤한 고유값으로 그냥 바꾼다
            #9. error : 이미지가 아닐 때? (validation 로직으로)
            
        print(request.POST)
        #<QueryDict: {'host_id': ['22']}>
        host_id = request.POST.get('host_id')
        host_id = request.POST['host_id']
        print(type(host_id))
        files = request.FILES.getlist('files', None)
        print('000', files)
        #000 [<TemporaryUploadedFile: baGETTT_logo.png (image/png)>, <TemporaryUploadedFile: bagettt.key (application/vnd.apple.keynote)>]
        
        print('4444', data)
        #어떻게 꺼내지???(그 body말고 다른 body > host_id)
        host_id = host_id #body 안에 formdata는 모든 형식이 다 들어갈 수 있음 (엔드포인트 : 업로드)

        s3      = boto3.resource('s3') #client와 resource 방법 차이???
        
        url     = f"https://{AWS_STORAGE_BUCKET_NAME}.s3.{region}.amazonaws.com"
        for file in files:
            print('111', file)
            file._set_name(str(uuid.uuid4()))
            s3.Bucket(AWS_STORAGE_BUCKET_NAME).put_object(Key = f'{file}', Body = file)
            # s3.Bucket(AWS_STORAGE_BUCKET_NAME).put_object(Key=f'{file}', Body=open(file, 'rb'))
            # TypeError: expected str, bytes or os.PathLike object, not InMemoryUploadedFile
            
            image_url = url+"/"+f'{file}'
            print('222', image_url)
            Image.objects.create(
                image_url = image_url,
                host_id   = host_id
            )
        
        return JsonResponse({'result' : 'CREATED'}, status = 201)
profile
킴릴로

0개의 댓글