avatar
Octoping Blog

S3에 파일을 업로드할 때 파일 이름에 확장자를 붙이지 않아 생긴 이슈 (feat. 이미지 최적화)

S3에 파일을 업로드할 때는 꼭 확장자도 같이 붙여줍시다
BackendAWSS3Spring
2 months ago
·
5 min read

언틸 서비스에는 블로그에 업로드되는 이미지들을 저장하기 위해 AWS의 S3 서비스를 이용하고 있다. 파일을 업로드하면 해당 파일의 이름에 따라 해당 파일에 액세스할 수 있도록 URL을 제공하고 있는데, 언틸에서는 따로 파일의 확장자를 지정하지 않고 저장하고 있었다.

덕분에 url이 이렇게 나오고 있었다.
https://{언틸_S3_URL}/octoping/profile/1722161760968

물론 지금 생각해보면 왜 그랬을까 싶긴 한데... 열심히 개발하면서 아무 생각 없이 저질렀던 것 같다.

코드

@Profile("real")
@Service
class S3ImageUploadService(
    private val s3Config: S3Config,
    private val imageDataSaver: ImageDataSaver,
    private val profileReader: ProfileReader,
) : ImageUploadService {
    override fun uploadImage(request: ImageUploadRequest): ImageUploadResult {
        val blog = profileReader.findById(request.blogId)
        val filePath = createFilePath(blog.username, request.fileType)
        val imgUrl = uploadImageAndGetUrl(filePath, request)
        
        // ...
    }

    private fun createFilePath(username: String, fileType: FileType): String {
        val fileName = System.currentTimeMillis().toString()
        return username + "/" + fileType.storePath + "/" + fileName
    }
}

다음이 해당 부분의 코드다.

블로그의 username과 파일의 타입 (프로필 사진인지, 아티클 사진인지 등)을 받아 저장될 path를 생성하는 함수가 보인다. 여기에서 fileName은 현재 시각을 통해서 만들고 있는데. 보시다시피 확장자를 따로 삽입해주고 있지 않았다..

결국 이것들이 S3에 올라간 시점에서는 타입을 구분하기가 어려워진다.

문제

저장된 파일의 확장자가 없으니 S3의 url만 가지고는 어떤 파일인지를 알 수 없게 된다. 이것이 이미지인지, 동영상인지, 그냥 파일인지 알 수 없다. 물론 언틸의 경우 지금이야 이미지만 저장하고 있으니 괜찮지 않을까?

WebP 변환 이슈

이미지에도 종류가 여러 가지가 있다. jpg, png, webp, gif...

그 중 WebP는 구글이 웹페이지 로딩 속도를 높이기 위해 개발한 이미지 포맷인데, 기존 포맷들에 비해 용량을 크게 줄일 수 있어 많은 사이트들에서 웹 사이트의 로딩 속도를 향상시키기 위해 사용하고 있다.

그리고 보통 이미지의 WebP로의 변환은 CloudFrontOrigin Response에서 Lambda@Edge를 통해 on-the-fly 방식으로 이루어지게 된다.

until-1840

이 WebP로 변환하는 람다 함수에서는 Sharp라는 라이브러리를 사용한다. 이 라이브러리는 다양한 확장자의 이미지를 지원하지만 그럼에도 지원하지 않는 이미지 포맷이 존재한다. (ex. BMP 파일)

따라서 WebP 변환을 진행하는 함수에서 확장자 검사를 진행해야 하는데, 앞서 소개한 상황에서는 확장자를 지니고 있지 않으니 다음을 진행할 수가 없다.

해결

// Before
private fun createFilePath(username: String, fileType: FileType): String {
    val fileName = System.currentTimeMillis().toString()
    return username + "/" + fileType.storePath + "/" + fileName
}

// After
private fun createFilePath(username: String, fileType: FileType, contentType: String): String {
   val fileName = System.currentTimeMillis().toString()
   return username + "/" + fileType.storePath + "/" + fileName + "." + contentType.split("/")[1]
}

다음과 같이 파일 이름에 확장자를 추가해주었다.

MultipartFile에서 넘어오는 contentTypeimage/png와 같은 형식으로 주어지므로, contentType.split("/")[1]와 같은 형식으로 파싱해주면 된다.

until-1841

이제는 확장자가 잘 붙은 것을 볼 수 있다.. 😢
다시는 하지 말자 이런 실수


- 컬렉션 아티클






반갑습니다 😄