Django

Django to AWS S3 업로드간 발생하는 I/O operation on closed file 해결방법

친구들안녕 2021. 6. 21. 05:04

서론

필자는 Django rest api(DRF)와 React를 연결하여 500 에러가 난 증상에서 시작되었습니다.

필자의 해결 진행 순서대로 진행되므로 양해 바랍니다.

 

I/O operation on closed file에러

Django와 AWS의 S3를 연동 후 파일을 업로드하게 된다면 아래와 같은 에러 메시지를 받을 수 있다.

File "C:\Users\user\Desktop\project\venv\Desktop\lib\site-packages\storages\backends\s3boto3.py", line 447, in _save content.seek(0, os.SEEK_SET) ValueError: I/O operation on closed file.

 

이러한 증상들을 찾다보니 한 블로그에서 증상이 비슷한 글을 찾게 되었는데

그 블로그에서는 form딴에서 save를 할 때의 에러였다. 

AWS S3에는 이미지가 이미 업로드된 상태가 되고, 메모리에 파일이 상주하게 됩니다.
그럼 추후 작업후 commit=True일 때 save가 들어오게 되면
S3에서는 이미 닫힌 파일이기 때문에 저장할 때 에러가 발생하게 되죠.

 

 

필자와 동일한 증상이면서도 DRF가 아닌 form을 사용하기에 해결방법이 달랐다.

그러나 어떠한 이유에서 일어난 것 인지 알기 때문에 조금 더 찾아봤다.

 

 

그래서 찾은 방법으로는 2가지가 있었는데 들어가기 앞서 설명을 해야 할 부분이 있다.

위의 에러명대로 s3boto3.py에서 실패한 것이므로 s3boto3.py에 가서 코드를 추가하거나

또는 코드 파일을 프로젝트에 직접 생성하면 된다. (이름은 상관없이 코드만 적용되면 된다.)

그리고 그 추가한 내용을 settings.py 아래와 같이 새로운 코드를 쓴다는 설정을 적용해야 작동이 된다.

 

s3boto3.py에 설정 시

DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.CustomS3Boto3Storage'

 

Custom 파일 생성 시

필자는 CustomS3Boto3Storage.pyCustomS3Boto3Storage 클래스를 만들어줬다.

DEFAULT_FILE_STORAGE = 'CustomS3Boto3Storage.CustomS3Boto3Storage'

 

먼저 1번부터 보자면 필자는 실패했다.

동일하게 content.seek 부분에서 에러가 발생했다.(또는 parameters 에러)

그러나 많은 사람들이 이 코드로 정상적으로 작동했다는 댓글들이 많아 혹시 몰라 첨부한다.

class CustomS3Boto3Storage(S3Boto3Storage):
    
    def _save_content(self, obj, content, parameters):
        content.seek(0, os.SEEK_SET)
        content_autoclose = SpooledTemporaryFile()
        content_autoclose.write(content.read())
        super(CustomS3Boto3Storage, self)._save_content(obj, content_autoclose, parameters)
 
        if not content_autoclose.closed:
            content_autoclose.close()

 

 

2번째 방법으로는 성공했는데

원래 적혀있던 주석을 대충 해석하자면

boto3은 업로드 시 파일을 닫는데 백엔드는 여전히 열려있을 것으로 예상되므로

임시파일을 만들어 그 파일을 읽은 후, 다시 삭제해주는 일을 한다.

class CustomS3Boto3Storage(S3Boto3Storage):

    def _save(self, name, content):
        content.seek(0, os.SEEK_SET)
        with SpooledTemporaryFile() as content_autoclose:
            content_autoclose.write(content.read())
            return super(CustomS3Boto3Storage, self)._save(name, content_autoclose)

 

 

마치며

위의 코드뿐만 아니라 settings.py에도 boto3 storage 설정을 해줘야 한다는 점을 유의해야 한다.

 

Reference

https://gmyankee.tistory.com/281

https://stackoverflow.com/questions/61228944/i-o-operation-on-closed-file-in-django-with-imagekit

https://github.com/matthewwithanm/django-imagekit/issues/391#issuecomment-504678524

https://github.com/matthewwithanm/django-imagekit/issues/391#issuecomment-275367006