com.amazonaws.services.s3.model.AmazonS3Exception: The bucket is in this region: ap-northeast-2. Please use this region to retry the request (Service: Amazon S3; Status Code: 301; Error Code: PermanentRedirect; Request ID: BD5BRX85WBJPREEP; S3 Extended Request ID: hGMXHdkNTwaXJnMSXfsRC5aEg7TBV6oNcDRCZPS/a5aUTZ6TFFtrhncgGVieORBTpuCynhQpHL0=; Proxy: null)
at com.amazonaws.http.AmazonHttpClient$RequestExecutor.handleErrorResponse(AmazonHttpClient.java:1811) ~[aws-java-sdk-core-1.11.792.jar!/:na]
AmazonS3Exception: The bucket is in this region: ap-northeast-2. Please use this region to retry the request
버킷이 서울 리전이므로 구성파일도 서울로 맞춰야 한다는 이야기.
문제 해결 링크
https://velog.io/@hjin10833/AWS-S3-Error-Code-PermanentRedirect
이 링크에서는 해결책으로 @Bean에서 AmazonS3 가 아닌 AmazonS3Client를 반환하라고 되어 있다. 물론 DI를 받을 때의 타입도 AmazonS3Client가 되어야 한다.
(기존에 둘 다 AmazonS3로 잘 사용하고 있었던 나는 어안이 벙벙한 상황)
package com.growstory.global.aws.config;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class S3Config {
@Value("${cloud.aws.credentials.accessKey}")
private String accessKey;
@Value("${cloud.aws.credentials.secretKey}")
private String secretKey;
@Value("${cloud.aws.region.static}")
private String region;
@Bean
public AmazonS3Client amazonS3Client() { //⭐ 반환타입 변경 AmazonS3 -> AmazonS3Client
AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey);
return (AmazonS3Client) AmazonS3ClientBuilder //⭐ 형변환 AmazonS3 -> AmazonS3Client
.standard()
.withCredentials(new AWSStaticCredentialsProvider(credentials))
.withRegion(region)
.build();
}
}
package com.growstory.global.aws.service;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.CannedAccessControlList;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PutObjectRequest;
import com.amazonaws.services.s3.model.PutObjectResult;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.UUID;
@Slf4j
@Service
@RequiredArgsConstructor
public class S3Uploader {
private final AmazonS3Client amazonS3Client; //⭐ AmazonS3 -> AmazonS3Client
@Value("${cloud.aws.s3.bucket}")
private String bucket;
// MultipartFile을 전달받아 File로 전환한 후 S3에 업로드
public String uploadImageToS3(MultipartFile image, String type) {
String originName = image.getOriginalFilename(); //원본 파일 이름
String ext = originName.substring(originName.lastIndexOf(".")); // 확장자
String changedName = changedImageName(originName); // 변경된 이름
ObjectMetadata metadata = new ObjectMetadata(); // 메타데이터
metadata.setContentType(ext);
try {
log.info("##" + amazonS3Client.getRegion().toString());
log.info("##" + amazonS3Client.getRegionName());
PutObjectResult putObjectRequest = amazonS3Client.putObject(new PutObjectRequest(
bucket + "/" + type, changedName, image.getInputStream(), metadata)
.withCannedAcl(CannedAccessControlList.PublicRead)
);
} catch (IOException e) {
throw new RuntimeException(e);
}
String imageUrl = amazonS3Client.getUrl(bucket + "/" + type, changedName).toString();
return imageUrl;
}
public void deleteImageFromS3(String imageUrl, String type) {
if (imageUrl.contains("https://s3.ap-northeast-2.amazonaws.com/"+ bucket))
amazonS3Client.deleteObject(bucket + "/" + type, imageUrl.split("/")[6]);
}
// 이미지 이름 변경
// 이미지 이름이 겹칠 경우 충돌이 발생하기 때문에 고유 난수를 uuid로 생성하여 originName과 합해준다.
private static String changedImageName(String originName) {
String random = UUID.randomUUID().toString();
return random + originName;
}
}
AmazonS3Client 반환 타입을 S3Config.java
S3Uploader.java
두 파일 모두에서 수정해야할 뿐만 아니라 변수명도 일치시키지 않으면 설정값을 가져오지 못하는 문제를 발견했다.
amazonS3일 경우
yml 파일에서 버킷 설정값 (서울)을 읽어오지 못한다.
반면 amazonS3Client
을 변수값으로 둘 경우,
제대로 설정값을 읽어오는 것을 알 수 있다.