Waggle 프로젝트에서 이미지 업로드가 필요한 상황으로는 회원 프로필 이미지가 있습니다. 때문에 AWS S3를 구성하고 처음으로 Rest API 환경에서 이미지를 요청받고 처리하는 과정을 구현할 수 있었습니다.

이미지 업로드 기능을 구현한 과정을 정리해봤습니다.


1) S3 버킷 생성

이미지가 들어갈 수 있는 공간을 생성하기 위해 S3 서비스에서 버킷을 생성했습니다.

해당 버킷의 파일을 Get하는 권한을 부여하기 위해 권한->버킷정책에서 아래와 같이 권한을 설정했습니다.

{
    "Version": "2012-10-17",
    "Id": "Policy1738159925394",
    "Statement": [
        {
            "Sid": "Stmt1738159922905",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::waggle-bucket/*"
        }
    ]
}

2) 이미지 파일 받아오기

2-1. Multipart 적용하기

사용자 정보를 수정할 때 HTTP Request 정보에 수정할 사용자 정보들이 들어있을 것이고, 그 중 사용자 프로필 이미지도 포함되어 있습니다. 해당 이미지 파일을 HTTP Request로 받아오기 위해 Multipart를 사용했습니다.

@RequestPart를 활용하여 이미지 파일은 이미지 파일대로, 나머지 텍스트 값은 DTO를 통해 받아오도록 구성했습니다.

// 사용자 정보 수정 컨트롤러
 
public ResponseEntity<BaseResponse<Object>> updateUser(  
        @RequestPart(value = "profileImage", required = false) MultipartFile profileImage,  
        @RequestPart(value = "updateUserDto") UpdateUserDto updateUserDto  
) {  
    User updatedUser = userService.updateUser(profileImage, updateUserDto);  
    return SuccessResponse.of(ApiStatus._OK, updatedUser);  
}

결과적으로 아래와 같이 Swagger에서 파일을 첨부하여 요청할 수 있게 되었습니다.

2-2. Swagger 호출 오류 해결

그러나 오류가 발생했고 디버깅 결과 profileImage에 null이 들어오는 것이 확인되었습니다.

Swagger 요청 중 HttpMediaTypeNotSupportedException: Content-Type 'application/octet-stream' is not supported 오류가 발생한 이유

Swagger UI에서 multipart/form-data를 사용하여 JSON 데이터를 전송할 때 Content-Type이 application/octet-stream으로 설정된다는 이슈를 확인하였습니다.

해결 방안

이 문제점을 해결하기 위해 WebMvcConfigurer에서 application/octet-stream도 처리를 할 수 있도록 설정했습니다.

@Configuration  
public class WebConfig implements WebMvcConfigurer {  
    private OctetStreamReadMsgConverter octetStreamReadMsgConverter;  
  
    @Autowired  
    public WebConfig(OctetStreamReadMsgConverter octetStreamReadMsgConverter) {  
        this.octetStreamReadMsgConverter = octetStreamReadMsgConverter;  
    }  
  
    @Override  
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {  
        converters.add(octetStreamReadMsgConverter);  
    }
}

결과적으로 Swagger에서도 Multipart를 인자로 받는 API를 사용할 수 있게 되었습니다.

3) S3 기능 구현하기

이미지 파일을 받아오는 데에 성공했으면 S3 버킷에 추가하거나, 삭제하는 기능 등이 필요합니다. 때문에 S3Service라는 클래스를 따로 만들어 필요할 때에 호출할 수 있도록 했습니다.

@Service  
@RequiredArgsConstructor  
public class S3Service {  
  
    private final AmazonS3 amazonS3;  
  
    @Value("${AWS_BUCKET}")  
    private String bucket;  
  
    public String uploadFile(MultipartFile file, String fileName) {  
        validateFile(file);  
  
        try {  
            ObjectMetadata metadata = new ObjectMetadata();  
            metadata.setContentType(file.getContentType());  
            metadata.setContentLength(file.getSize());  
  
            amazonS3.putObject(bucket, fileName, file.getInputStream(), metadata);  
            return amazonS3.getUrl(bucket, fileName).toString();  
        } catch (IOException | AmazonS3Exception e) {  
            throw new S3Exception(ApiStatus._S3_UPLOAD_FAILED, e);  
        }  
    }  
  
    public void deleteFile(String fileUrl) {  
        try {  
            String fileName = extractfilenamefromurl(fileUrl);  
            amazonS3.deleteObject(bucket, fileName);  
        } catch (Exception e) {  
            throw new S3Exception(ApiStatus._S3_DELETE_FAILED, e);  
        }  
    }
 
	...
}