본격적으로 프로젝트에서 이미지를 S3 bucket으로 업로드 하기 위해
업로드 테스트 스크립트를 작성해 보았다.
1. bulid.graddle
//aws
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
implementation "com.amazonaws:aws-java-sdk-s3:1.12.395"
aws 관련 implementation을 추가해 주었다.
2. application.yml
# multipart 설정
spring.servlet.multipart.max-file-size: 10MB
spring.servlet.multipart.max-request-size: 10MB
# aws 설정
cloud:
aws:
credentials:
accessKey: accessKey
secretKey: secretKey
region:
static: ap-northeast-2
stack:
auto: false
s3:
bucket: airplanning-bucket
yml에서 multipart의 max file size와 max request size를 10 MB로 제한하였다.
기본 default는 각각 1 MB이다.
aws에서 사용자에 지정한 IAM accessKey와 secretKey를 값을 불러올 수 있도록 설정하였다.
실제 값은 환경변수를 통해 설정하였다. 노출되면 금전적 피해가 발생 할 수 있으니 노출에 조심하도록 하자.
region.static에는 s3이 띄워져 있는 region을 설정하였고, 해당 지역은 서울에 해당된다.
stack.auto는 false로 지정해 주어야한다.
Spring Cloud AWS는 기본적으로 서비스가 Cloud Formation 스택내에서 실행된다고 가정하기 때문에, false로 지정해 주지 않으면 No valid Instance error 가 발생한다.
s3.bucket은 s3 bucket 생성시 bucket 명을 입력한다.
혹, bucket내 directory에 업로드 하고 싶으면, bucketname/dir 식으로 경로를 지정해 주어도 된다.
<Reference>
3. AWSConfig
@Configuration
public class AWSConfig {
@Value("${cloud.aws.credentials.accessKey}")
private String iamAccessKey; // IAM Access Key
@Value("${cloud.aws.credentials.secretKey}")
private String iamSecretKey; // IAM Secret Key
private String region = "ap-northeast-2"; // Bucket Region (서울)
@Bean
public AmazonS3Client amazonS3Client() {
BasicAWSCredentials basicAWSCredentials = new BasicAWSCredentials(iamAccessKey, iamSecretKey);
return (AmazonS3Client) AmazonS3ClientBuilder.standard()
.withRegion(region)
.withCredentials(new AWSStaticCredentialsProvider(basicAWSCredentials))
.build();
}
}
AWS에 접근 할 때, 어떤 IAM을 통해 접근하고, 어떤 지역에서 접근하는지를 설정하는 Configuration이다.
얻은 자격증명 객체를 이용해서 AmazonS3ClientBuilder를 통해 S3 Client를 가져온다.
<Reference>
4. Controller
@RestController
@RequestMapping("/upload")
@RequiredArgsConstructor
public class FileUploadTestController {
private final S3FileUploadTestService s3FileUploadTestService;
@PostMapping
public Response<String> uploadFile(@RequestPart("file") MultipartFile file) throws IOException {
String url = s3FileUploadTestService.uploadFile(file);
return Response.success(url);
}
}
업로드할 file을 받아서 service에서 업로드를 진행하여 해당 file이 업로드된 s3의 url을 받아 return 해준다.
return된 url을 따라 접속하면, 업로드한 이미지를 확인 할 수 있다.
<Reference>
https://chordplaylist.tistory.com/261
5. Service
@RequiredArgsConstructor
@Service
public class S3FileUploadTestService {
private final AmazonS3Client amazonS3;
@Value("${cloud.aws.s3.bucket}")
private String bucketName;
private String dir = "/test1";
private String defaultUrl = "https://airplanning-bucket.s3.ap-northeast-2.amazonaws.com";
public String uploadFile(MultipartFile file) throws IOException {
String bucketDir = bucketName + dir;
String dirUrl = defaultUrl + dir + "/";
String fileName = generateFileName(file);
amazonS3.putObject(bucketDir, fileName, file.getInputStream(), getObjectMetadata(file));
return dirUrl + fileName;
}
private ObjectMetadata getObjectMetadata(MultipartFile file) {
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setContentType(file.getContentType());
objectMetadata.setContentLength(file.getSize());
return objectMetadata;
}
private String generateFileName(MultipartFile file) {
return UUID.randomUUID().toString() + "-" + file.getOriginalFilename();
}
}
getObjectMetadata
S3에 업로드 하기 위해 multipartfile의 사이즈와 타입을 S3에 알려주기 위해서 ObjectMetadata를 사용한다.
metadata로 contentLength를 설정하지 않으면 S3에 업로드는 되나, 다음과 같은 에러로그가 나온다고 한다.
: [io-8443-exec-65] c.amazonaws.services.s3.AmazonS3Client : No content length specified for stream data.
metadata contentType을 설정하지않으면 url으로 접속시에 무조건 다운로드가 되게 된다.
하지만 contentType을 위와 같이 설정하게 되면, 바로 다운로드가 아닌 이미지 조회만도 가능하다.
generateFileName
s3에 파일을 저장할 때 이미지 파일 이름 중복 예방을 위해 UUID를 사용하였다.
UUID로 생성한 랜덤 String과 file의 originalname을 "-"로 연결하여 filename을 설정하였다.
uploadFile
bucketDir은 bucketname과 bucket안에 파일 저장을 원하는 directory name을 연결하여 지정하였다.
dirUrl은 bucket에 저장될 때 파일의 기본 url로 설정되는 defaultUrl에 directory name을 연결하여 지정하였다.
그 후, S3 API 메소드인 putObject를 이용하여 S3에 파일을 업로드를 하도록 하였다.
return값은 업로드된 파일을 조회할 수 있는 url으로 지정하였다.
<Reference>
https://chordplaylist.tistory.com/261
https://devlog-wjdrbs96.tistory.com/323
Error
위의 설정을 하고 SpringBoot를 실행 할 때, 아래와 같은 오류가 발생한다.
InstanceMetadataServiceResourceFetcher 클래스의 readResource를 호출하면서 발생한 에러다.
서비스의 endpoint를 연결하지 못해 발생하는 에러라고 한다.
EC2의 메타데이터를 읽다가 발생하는 에러로써, EC2인스턴스가 아닌 곳에서는 의미가 없는 에러다.
spring-cloud-starter-aws 의존성 주입시 로컬환경은, aws환경이 아니기때문에 나는 에러라고 한다.
다만, timed out 하는동안 다음 프로세스를 진행하지 못하기 때문에 시간 지연이 발생하여 로직을 발생시키지 않도록 하는 것이 좋다.
아래의 구문을 vm options에 추가하여 해결이 가능하다.
-Dcom.amazonaws.sdk.disableEc2Metadata=true
<Reference>
https://lemontia.tistory.com/1006
https://thalals.tistory.com/289
https://earth-95.tistory.com/117
<전체 Reference>
https://antdev.tistory.com/93
https://doing7.tistory.com/45
https://chordplaylist.tistory.com/261
https://devlog-wjdrbs96.tistory.com/323
https://lemontia.tistory.com/1006
https://thalals.tistory.com/289
https://earth-95.tistory.com/117
'SpringBoot' 카테고리의 다른 글
[SpringBoot] userRole에 따른 접근 제한 시 페이지 이동 + 메세지 출력 (0) | 2023.02.08 |
---|