기본 세팅
먼저 S3 버킷에 대한 퍼블릭 엑세스 설정이 필요하다. 해당 글을 참고하자.
2022.07.25 - [aws] - [AWS] S3 버킷 퍼블릭 엑세스 설정
또한, Spring Boot에서 S3 설정 및 접근에 대한 인증 키를 입력해야 한다. 해당 글을 참고하자.
2022.07.25 - [java/spring] - [Spring] AWS S3 접근
S3 객체 조회
S3 버킷에 있는 객체들을 조회하고 싶을 때가 있다. 이 글에서는 두 가지 조회 방식을 소개한다.
1. S3 버킷or디렉토리 하위 객체 전체 조회
2. S3 버킷or디렉토리 level의 객체만 조회
예를들어 S3 버킷의 구조가 다음과 같다고 하자.
버킷
디렉토리 1
파일 1
파일 2
디렉토리 2
파일 3
1번 방식으로 버킷을 조회하면 버킷 하위 모든 객체를 조회한다 -> 디렉토리 1, 파일 1, 파일 2, 디렉토리 2, 파일 3
2번 방식으로 버킷을 조회하면 버킷 level의 객체만 조회한다 -> 디렉토리 1, 디렉토리 2, 파일 3
두 가지 방식에 대해 이해가 되었을 것이다. 이제 각각 코드를 살펴보자.
1. S3 버킷 하위 객체 전체 조회
먼저 컨트롤러를 하나 만든다.
@RestController
@RequiredArgsConstructor
@RequestMapping("/s3")
public class S3Controller {
private final S3Service s3Service;
@GetMapping("/{bucket}/**")
public ResponseEntity<S3GetResponseDto> find(@PathVariable String bucket, HttpServletRequest request) {
String[] split = request.getRequestURI().split("/s3/" + bucket);
String prefix = split.length < 2 ? "" : split[1].substring(1);
S3GetResponseDto s3GetResponseDto = s3Service.find(bucket, prefix);
return new ResponseEntity<>(s3GetResponseDto, HttpStatus.OK);
}
}
1. URI 경로로 bucket과 prefix(디렉토리 경로)를 받는다.
2. prefix는 @Pathvariable이 아닌 '/**' 형태로 받은 이유는 /aaa/bbb/... 와 같은 path 형태로 파라미터를 받기 위해서이다.
find 메서드의 위 두 줄은 prefix를 파싱하는 코드다.
3. s3Service.find(bucket, prefix)를 호출한다.
4. 생성된 S3GetResponseDto를 리턴한다.
S3GetResponseDto는 다음과 같다.
@Data
@AllArgsConstructor
public class S3GetResponseDto {
private List<String> fileNames;
public static S3GetResponseDto from(List<String> fileNames) {
return new S3GetResponseDto(fileNames);
}
}
이제 서비스 로직을 작성하자.
public S3GetResponseDto find(String bucket, String prefix) {
List<String> fileNames = new ArrayList<>();
ListObjectsRequest listObjectsRequest = new ListObjectsRequest();
listObjectsRequest.setBucketName(bucket);
if (!prefix.equals("")) {
listObjectsRequest.setPrefix(prefix);
}
ObjectListing s3Objects;
do {
s3Objects = amazonS3.listObjects(listObjectsRequest);
for (S3ObjectSummary s3ObjectSummary : s3Objects.getObjectSummaries()) {
fileNames.add(s3ObjectSummary.getKey());
}
listObjectsRequest.setMarker(s3Objects.getNextMarker());
} while (s3Objects.isTruncated());
return S3GetResponseDto.from(fileNames);
}
listObjects()는 객체를 1,000개 씩 조회한다.
만약, 디렉토리 하위 객체가 1,000개가 넘는 경우는 위와 같이 do ~ while()문을 사용해야 한다.
간혹, "listObjectRequest.setMaker(s3Objects.getNextMaker())" 방식을 사용하지 않고,
"s3Objects = amazonS3.listNextBatchOfObjects(s3Objects)" 방식을 사용하는 코드도 있는데, 이는 정확히 1,000개 단위로만 가져오기 때문에, 객체 전체를 가져올 수 없는 경우가 생긴다.
예를 들어, 객체가 2,500개인 경우,
첫 번째 방식은 1,000 -> 1,000 -> 500 순으로 잘 가져오는 반면,
두 번째 방식은 1,000 -> 1,000 까지만 가져온다.
결국 첫 번째 방식인 위 코드를 사용하자.API를 호출하면 "디렉토리/객체" 형식으로 객체를 조회하는 것을 확인할 수 있다.ex) http://localhost:8080/s3/bucket/prefix
{
"fileNames": [
"prefix/",
"prefix/file1",
"prefix/file2",
"prefix/file3",
...
]
}
2. S3 버킷 level의 객체만 조회
만약 객체 전체 조회가 아닌, 해당 디렉토리 레벨의 객체만 조회하고 싶을 때가 있다.
마치 폴더를 클릭 클릭해서 이동하는 컴퓨터 탐색기처럼 작동하는 방식이다.
이런 경우, 서비스 로직은 다음과 같이 작성한다. (컨트롤러, ResponseDto는 1번 방식과 같다)
public S3GetResponseDto find(String bucket, String prefix) {
List<String> fileNames = new ArrayList<>();
ListObjectsRequest listObjectsRequest = new ListObjectsRequest();
listObjectsRequest.setBucketName(bucket);
if (!prefix.equals("")) {
listObjectsRequest.setPrefix(prefix + "/");
}
listObjectsRequest.setDelimiter("/");
ObjectListing s3Objects;
String fileName;
do {
s3Objects = amazonS3.listObjects(listObjectsRequest);
for (String commonPrefix : s3Objects.getCommonPrefixes()) {
if (!prefix.equals("")) {
fileName = commonPrefix.split(prefix + "/")[1];
} else {
fileName = commonPrefix;
}
fileNames.add(fileName);
}
listObjectsRequest.setMarker(s3Objects.getNextMarker());
} while (s3Objects.isTruncated());
return S3GetResponseDto.from(fileNames);
}
1번 방식에서 listObjectsRequest.setDelimiter("/")가 추가함으로써, 디렉토리 단위로 조회하도록 한다.
s3Objects.getObjectSummaries()가 아닌 s3Objects.getCommonPrefixes()를 통해 조회한다.
API를 호출하면 "디렉토리/" 형식으로 객체를 조회하는 것을 확인할 수 있다.
ex) http://localhost:8080/s3/bucket/prefix
{
"fileNames": [
"디렉토리 1/",
"디렉토리 2/",
...
]
}
그러나 이 방식에는 한 가지 문제점이 있다. 바로 파일에는 접근할 수 없다는 것이다.
아까 예시로 들었던 버킷 구조를 다시 가져와보자.
버킷
디렉토리 1
파일 1
파일 2
디렉토리 2
파일 3
만약 여기서, http://localhost:8080/s3/bucket을 호출하면 다음과 같이 조회되는 것이 정상이다.
디렉토리 1
디렉토리 2
파일 3
그러나 s3Objects.getCommonPrefixes()는 디렉토리 밖에 조회하지 못한다.
따라서, s3Objects.getObjectSummaries()를 통해 파일 역시 조회할 수 있는 코드를 추가한다.
public S3GetResponseDto findOld(String bucket, String prefix) {
List<String> fileNames = new ArrayList<>();
ListObjectsRequest listObjectsRequest = new ListObjectsRequest();
listObjectsRequest.setBucketName(bucket);
if (!prefix.equals("")) {
listObjectsRequest.setPrefix(prefix + "/");
}
listObjectsRequest.setDelimiter("/");
ObjectListing s3Objects;
String fileName;
do {
s3Objects = amazonS3.listObjects(listObjectsRequest);
for (String commonPrefix : s3Objects.getCommonPrefixes()) { // prefix 경로의 디렉토리를 저장 (ex. v1/)
if (!prefix.equals("")) {
fileName = commonPrefix.split(prefix + "/")[1];
} else {
fileName = commonPrefix;
}
fileNames.add(fileName);
}
// 파일 조회 코드 추가
for (S3ObjectSummary objectSummary : s3Objects.getObjectSummaries()) { // prefix 경로의 파일명이 있다면 저장 (ex. one.wav)
String key = objectSummary.getKey();
String[] split = key.split(prefix + "/");
if (split.length >= 2) {
fileNames.add(split[1]);
}
}
listObjectsRequest.setMarker(s3Objects.getNextMarker()); // listObjects()는 1,000건만 조회, 파일 수가 그 이상인 경우를 대비
} while (s3Objects.isTruncated());
return S3GetResponseDto.from(fileNames);
}
이제 파일 탐색기처럼 정상 출력되는 것을 확인할수 있다.
{
"fileNames": [
"디렉토리 1/",
"디렉토리 2/",
"파일 3",
...
]
}
S3에서 객체를 조회하는 두 가지 방식을 알아보았다.
첫 번째 방식은 S3 버킷의 객체 전체 개수를 파악할 때 용이하고,
두 번째 방식은 실제 파일 탐색기처럼 동작하는 등 화면 구성, UX에 더 적합하다고 생각한다.
'java > spring' 카테고리의 다른 글
[Spring] MariaDB connection 끊김 (0) | 2022.08.03 |
---|---|
[Spring] Failed to validate connection (0) | 2022.08.03 |
[Spring] AWS S3 접근 (0) | 2022.07.25 |
[Spring] S3 Pre-Signed URL 생성 (0) | 2022.07.25 |
[Spring] @Value, static 변수에 사용하기 (0) | 2022.07.22 |