[Error] [AWS] S3 Error Code: PermanentRedirect

NtoZ·2023년 11월 14일
0

error

목록 보기
7/7

에러 상황

  • 갑자기 잘 되던 S3 업로드에서 문제 발생
  • 로그를 뒤져보니 다음 에러 로그가 출력됨.
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로 잘 사용하고 있었던 나는 어안이 벙벙한 상황)

S3Config.java

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();
    }
}

S3Uploader.java

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을 변수값으로 둘 경우,

    제대로 설정값을 읽어오는 것을 알 수 있다.

profile
9에서 0으로, 백엔드 개발블로그

0개의 댓글