이미지의 Content-Type이 이상하게 올라가요....
회사를 다니면서 겪었던 이슈 중 가장 난해했던 이슈였다.
문제 발생
회사를 옮기고 해당 회사에서 어느정도 도메인 및 서비스에 대해 익어갈 때쯤 겪었던 이슈였는데
슬랙에서 보통 에러관련해서 메시지가 전사채널에 오는데 이거하고 관련된 이슈면 대부분 메시지 내용은 아래와 같았다.
- 이미지가 엑박으로 떠요
- 배경 이미지가 삭제가 안되어져요
그러면 S3에 들어가 해당 object를 확인해보면 분명히 확장자는 이미지인데 MetaData 에 있는 Content-Type이 이상하게 들어가있었다.
- application/json
- text/javascript
그렇게 되어지니 CloudFront 를 통해서 Lambda 가 이미지에 대해 변형을 시도할 때 에러가 발생해서 엑박이 뜨거나
다른 외부 API 를 통해서 배경을 삭제하려고 하는데 에러가 발생하는 경우 이렇게 두개의 건이 많았다.
시도
일단 해당 오류는 잡기가 너무 어려웠다.
코드가 순수하게 django 에서 지원하는 python에서 지원하는 것만 사용되어지다보니 알 수가 없었다.
models.ImageField() # 이것만 사용
그리고 계속적으로 발생하는 것이 아닌 어쩌다가 발생하는 경우이고 또 발생한다해도 이미 이미지 파일은 예전에 올라가서 에러 시기를 잡을 수 없다는 거였다.
그래서 파일의 content-type 관련해서 연관있는 코드를 확인해보니 따로 수동적으로 세팅이 되어진 부분은 없었고
순순 python 과 boto를 통해서 돌아가는 것을 알 수 있었다.
그래서 혹시 이부분이 python 과 boto 가 추후 업데이트하면서 바뀌었던 포인트가 있었을까? 하고 찾아보니 있었고 혹시 그러면 이부분은 업데이트만 하면 해결이 되어질 수도 있다고 생각했다.
약 해당 오류를 접하고 7개월 정도만에 업데이트를 진행했고 해결이 되어진 줄 알았었다.
왜냐하면 오류관련 메시지가 오지 않았기 때문이다.
다시 발생하는 에러
그러다가 재택으로 근무하는 도중 이미지의 배경이 삭제가 안되어지고 해당 이미지를 보니 엑박으로 떠요라는 메시지가 개인 DM 으로 왔었다.
그래서 순간의 감정은 !!!!!! 이거였고 해결된 거 아니였었나... 하면서 속으로 생각을 했다.
그러면서 에이 그래도 이미지 재업로드하면 문제 없겠지 하면서 호기롭게 제가 한번 다시 이미지 업로드 할게요
그러면 일단은 해결되어질 것 같아요 하면서 DM을 보냈고 이미지를 업로드하고 해결이 되어질 줄 알았다.
이미지를 올리고 S3에서 Contetn-Type을 확인해보니 무슨 이런일이... application/json으로 올라가는 것이었다.
계속 올려도 이것은 동일했다.
그러면서 이거는 어!!! 문제다라고 생각했고 도대체 어디에서 문제가 발생하는 거지? 하면서 등줄기에 식은 땀이 흘렀다.
기시감
그러면서 생각했다. application/json 이라는 Content-Type 왜 이렇게 기시감이 들지? 하면서 머릿속을 곰곰히 생각해보니 딱 떠오르는 하나가 있었다.
백오피스에서 내가 하나의 파일을 올린 것이 있는데 그 파일의 Content-Type을 application/json 으로 설정했는데 혹시??? 하면서
그 백오피스에서 한번 Content-Type 을 이미지로 설정하고 업로드 후 다시 제품 image 파일을 넣었는데 갑자기 정상동작을 하기 시작했다.
그때 깨달았다 여기 관련된 코드가 무언가가 잘 못되어서 전체적으로 백오피스에서 이미지를 올릴 때 이슈가 생기는 구나 하고 말이다.
원인
아래의 코드가 문제가 있었던 코드다.
# admin.py
options_dict = {
"CacheControl": self.cleaned_data.get("cache_control"),
"ContentType": self.cleaned_data.get("contents_type"),
}
self.instance.upload_file.storage.object_parameters.update(options_dict)
보면 form을 통해서 받은 데이터를 storage 의 object_parameters 에 update를 한다.
참고로 저 storage는 django-storages 에 있는 s3가 사용하는 부분이다.(s3 기반으로 사용하고있어서)
그러면 저렇게 update를 하면 어떻게 동작을 하는지는 내부 라이브러리 코드를 보면 알 수 있는데
# django-storage 에 있는 s3.py
class S3Storage(CompressStorageMixin, BaseStorage):
...
def get_default_settings(self):
return {
...
"object_parameters": setting("AWS_S3_OBJECT_PARAMETERS", {}),
}
def _get_write_parameters(self, name, content=None):
params = self.get_object_parameters(name)
if "ContentType" not in params:
_type, encoding = mimetypes.guess_type(name)
content_type = getattr(content, "content_type", None)
content_type = content_type or _type or self.default_content_type
params["ContentType"] = content_type
if encoding:
params["ContentEncoding"] = encoding
if "ACL" not in params and self.default_acl:
params["ACL"] = self.default_acl
return params
저기 보면 object_parameter 의 default 값은 빈 dict이다. 그러면 대부분은 _get_write_parameters 에서 python 이 제공하는 mimetypes.guess_type 을 통해서 올바른 content-type 이 설정되어진다.
그런데 object_parameter 를 업데이트를 하고 심지어 ContentType이라는 key 가 설정되어지면 저기에 있는 순수하게 돌아가야하는 부분들이 정상적으로 동작을 안하고 object_parameter 에 값으로만 돌아간다.
저기 같은 경우는 서버가 다시 재시동을 하거나 혹은 어쩌다 우연히 이미지 파일로 올라갈 때만 정상적으로 동작하는 것처럼 보이게 되어지는 것이다.
해결
일단 해결은 간단했다.
저 부분을 없앴고 ContentType, CacheControl 같은 경우는 따로 파일이 올라간 이후 수정을 자동적으로 하는 형태로 변경해서 작업을 진행했다.
그러면서 입사하기 이전부터 오랫동안 유서 깊게 발생하던 오류를 수정하는 순간이었다.
후기
해당 오류는 솔직히 개인적으로 너무 찾기 힘든 오류였다.
이유는
- 내가 입사하기 이전에 작성되어진 코드
- 전역적으로 관리하는 settings 가 아닌 다른 쪽에서 의도치 않게 사용되어진 부분
- 배포 혹은 다른 파일을 해당 경로로 올릴 경우 발생을 안한다.
- 문제를 겪는 사용자와 인스턴스가 겹치지 않는 경우 해당 에러를 접할 수 없다.
- 간혈적으로 발생하는 이슈
- 테스트 코드 없음
이러한 부분이 정말로 힘들게 했고 진짜 우연히도 해당 오류를 제보를 받음과 동시에 이전에 내가 application/json 파일을 올린 것과 같은 instance 에 접속을 하고 있었던 것이 겹쳐서 해당 오류를 해결할 수 있었다.
아니었으면 해결하는데 엄청 오래걸리지 않았을까 생각한다.