명품몰에 한글 파일명으로 올린 상품 이미지가 안 열리고 주소에 %25ED%2595%259C 처럼 퍼센트 기호가 이중으로 들어간 문자열이 보였다. 이중 인코딩처럼 보여서 주소를 만드는 쪽을 의심했다.
Table of contents
Open Table of contents
증상 — 목록에는 한글로 보이던 키
오브젝트 스토리지 콘솔에서 버킷을 목록으로 조회하니 파일명이 한글로 보였다. 그러면 저장은 정상이고 주소 생성이 잘못된 것으로 읽힌다.
그런데 한글 키로 doesObjectExist() 를 부르니 없다는 응답이 왔다. 목록에 보이는 것과 실제로 조회되는 것이 어긋나 있었다.
검증 — 원시 키와 개별 조회의 교차
listObjects() 로 원시 키를 그대로 출력하는 방식으로 다시 보니 %ED%95%9C%EA%B8%80.jpg 같은 퍼센트 인코딩된 문자열이 실제 키였다. 콘솔이 표시할 때 디코딩해서 보여 주고 있었던 것이다.
도구가 보여 주는 것과 실제 저장된 값이 다를 수 있으므로 원시 키 조회와 개별 존재 확인을 교차해야 확정된다. 한글 키로 없고 인코딩 키로 있으면 인코딩된 문자열이 실제 이름이라는 뜻이다.
원인 — 이중 인코딩이 정상 경로였다
그러면 지금까지 어떻게 동작했는지가 남는다. 우리 코드가 저장된 파일명을 주소에 넣으면서 URLEncoder 로 한 번 더 인코딩하고 있었다.
RFC 3986 대로 % 자체도 인코딩 대상이라 %ED 는 %25ED 가 되고, 두 번 인코딩된 주소를 서버가 한 번 디코딩하면 실제 키와 일치한다. 이중 인코딩처럼 보이던 것이 우연이 아니라 일관되게 동작하는 정상 경로였다.
디코딩 없이 저장한 지점
거슬러 올라가니 브랜드 사이트 주소에서 이미지를 받아오는 ImageFetcher 가 주소의 인코딩된 부분을 URLDecoder 로 풀지 않고 그대로 파일명으로 저장하고 있었다. 저장소에도 그 문자열 그대로 올라갔다.
copyObject() 를 쓰는 복제 함수는 그 값을 그대로 물려주므로 복제본도 같은 상태가 된다. 수만 건이 이미 그 형태로 굳어 있었다.
판단 기준 — 부분 정상화가 가장 나쁜 이유
두 가지 방법이 있었다. 저장소 키를 정본으로 보고 지금 상태를 유지하는 것과, 업로드에 디코딩을 넣고 기존 키를 전부 이름 변경하는 것이다. 앞쪽을 골랐다. 이유는 뒤쪽이 옳아 보이지만 수만 건 이름 변경 중에 접근하면 안 열리고 그 비용을 감당할 시기가 아니어서다. 대신 코드에 이중 인코딩이 의도된 경로라는 주석을 남겨야 하는 것을 감수했다.
확실한 것은 신규만 한글로 저장하는 방식은 하면 안 된다는 것이었다. 저장소에 두 형식이 섞이면 조회 코드가 어느 쪽인지 판단할 방법이 없어져서 섞인 상태가 어느 한쪽으로 통일된 상태보다 나쁘다.
정리
- 스토리지 콘솔은 표시할 때 디코딩해서 보여 준다
- 도구가 보여 주는 것과 실제 저장된 값이 다를 수 있다
listObjects()원시 키와doesObjectExist()개별 확인을 교차해야 확정된다- 이중 인코딩처럼 보이는 것이 정상 동작 경로일 수 있다
- 근원은 대개
URLDecoder없이 그대로 저장한 지점이다 copyObject()는 그 값을 그대로 물려준다- 정상화는 한쪽을 정본으로 삼는 두 갈래 중 하나로 간다
- 부분 정상화는 섞인 상태를 만들어 가장 나쁘다