์ผ | ์ | ํ | ์ | ๋ชฉ | ๊ธ | ํ |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- ํ๋ก๊ทธ๋๋จธ์ค
- ์ ํจ์ค ์ค์ผ์ค๋ฌ
- prod docker-compose
- Kafka
- Codedeploy ์ค๋ฅ
- JPA
- aws ์ฟ ํฐ
- ์ ํจ์ค ๋น๋ ์ค๋ฅ
- ์คํํ๋ ๋ฏธ์ค
- redis ์กฐํ
- docker compose
- ํ์ดํผ๋ฐ์ด์
- ํ๋ก๊ทธ๋๋จธ์ค ์ปฌ๋ฌ๋ง๋ถ
- s3 ์ด๋ฏธ์ง ์ ์ฅ
- Entity
- nGrinder
- ๋ค์ค ์ปจํ ์ด๋
- docker-compose kafka
- redis ํ ์คํธ์ฝ๋
- docker
- s3 ์ด๋ฏธ์ง ๋ค์ด๋ก๋
- aws saa ํฉ๊ฒฉ
- private subnet ec2 ๋ก์ปฌ ์ ์
- ์๋ฐ
- ํ๋ก๊ทธ๋๋จธ์ค ํฉ์นํ์์๊ธ
- AWS Certified Solutions Architect - Associate
- s3 log ์ ์ฅ
- ์๋ฒ ํฐ์ง๋ ๋์ปค ์ฌ์คํ
- docker ps -a
- jvm ๋ฐ๋ฐ๋ฅ๊น์ง ํํค์น๊ธฐ
- Today
- Total
๐๐ข๐๐ โ๐๐๐ ๐๐๐ก๐๐ ๐๐๐๐โง
[AWS] ์ฒจ๋ถํ์ผ AWS S3์ ์ ์ฅ & ๋ค์ด๋ก๋ํ๊ธฐ ๋ณธ๋ฌธ
[AWS] ์ฒจ๋ถํ์ผ AWS S3์ ์ ์ฅ & ๋ค์ด๋ก๋ํ๊ธฐ
๐คRyusun๐ค 2024. 3. 18. 19:45๊ณผ๊ฑฐ ํ๋ก์ ํธ์์ ๋ก์ปฌ์ ์ด๋ฏธ์ง๋ ํ์ผ์ ์ ์ฅ, ์กฐํ, ๋ค์ด๋ก๋ ํ๋ ๊ธฐ๋ฅ์ ๊ตฌํํ์ ์๋ค.
์ด๊ฑธ ์ด์ S3์ ์ ์ฅํด๋ณด๊ณ ์ถ์ด ํ๋ฒ ๋ฆฌํฉํ ๋ง์ ํด๋ณด์๋ค.
์ฐ์ ๋ฉํฐํํธ(Multipart) ํ์ผ ๊ด๋ จ ์ค์ ์ ํด์ค์ผํ๋ค.
spring:
servlet:
multipart:
enabled: true # ๋ฉํฐํํธ ์
๋ก๋ ์ง์์ฌ๋ถ (default: true)
max-file-size: 100MB # ํ๊ฐ ํ์ผ์ ์ต๋ ์ฌ์ด์ฆ (default: 1MB)
max-request-size: 100MB #
์ด์ S3๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ถ๊ฐํด์ผํ๋ค.
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
๊ทธ๋ฆฌ๊ณ S3 ๋ฒํท๋ฅผ ๋ง๋ค์ด์ค๋ค.
ํด๋น ๋ฒํท์ ๋จ์ ํ๋ก์ ํธ์ฉ์ด๋ฏ๋ก ๋ณด์์ ๊ฐ๋จํ๊ฒ ํด์ฃผ๊ฒ ๋ค.
๋ณด์์ด ํ์ํ๊ฒฝ์ฐ ํผ๋ธ๋ฆญ ์ก์ธ์ค๋ฅผ ์ฐจ๋จํ๋ฉด ์๋๋ค.
๋ฒํท์ ์์ฑํํ IAM์ผ๋ก ๊ฐ์ s3 ์ญํ ์ ๋ถ์ฌ๋ฐ์ ์ฌ์ฉ์๋ฅผ ๋ง๋ค์ด์ค๋ค.
ํ์ ์ด ์ฌ์ฉ์์ accesskey๋ฅผ ๊ฐ์ง๊ณ ๋ฒํท์ ์ ๋ก๋๋ฅผ ํ ๊ฒ์ด๋ค.
IAM ์ฌ์ฉ์๋ฅผ ๋ง๋ ํ S3FullAccess ์ ์ฑ ์ ๋ถ์ฌํ๋ค.
์์ฑํํ ์ค๋ฅธ์ชฝ ์๋จ์ ์ก์ธ์ค ํค๋ฅผ ๋ง๋ค๊ธฐ๋ฅผ ๋๋ฌ ์ก์ธ์คํค๋ฅผ ๋ง๋ค์ด์ค๋ค.
๋ฐ๊ธ๋ฐ๋ access-key, secret-key๋ฅผ application.yml์ ๋ฃ์ด์ค๋ค.
cloud:
aws:
s3:
bucket: <S3 ๋ฒํท ์ด๋ฆ>
credentials:
access-key: <์ ์ฅํด๋์ ์ก์ธ์ค ํค>
secret-key: <์ ์ฅํด๋์ ๋น๋ฐ ์ก์ธ์ค ํค>
region:
static: ap-northeast-2
auto: false
stack:
auto: false
cloud.aws.stack.auto=false
EC2์์ Spring Cloud ํ๋ก์ ํธ๋ฅผ ์คํ์ํค๋ฉด ๊ธฐ๋ณธ์ผ๋ก CloudFormation ๊ตฌ์ฑ์ ์์ํ๊ธฐ ๋๋ฌธ์ ์ค์ ํ CloudFormation์ด ์์ผ๋ฉด ํ๋ก์ ํธ ์คํ์ด ๋์ง ์๋๋ค. ํด๋น ๊ธฐ๋ฅ์ ์ฌ์ฉํ์ง ์๋๋ก false๋ก ์ค์ ํ๋ค.
S3Config.java
AWS S3(Simple Storage Service)์์ ํต์ ์ ์ํ AmazonS3Client ๋น(bean)์ ์์ฑํด์ผํ๋ค.
@Configuration
public class S3Config {
@Value("${cloud.aws.credentials.access-key}")
private String accessKey;
@Value("${cloud.aws.credentials.secret-key}")
private String secretKey;
@Value("${cloud.aws.region.static}")
private String region;
@Bean
public AmazonS3Client amazonS3Client() {
BasicAWSCredentials awsCredentials= new BasicAWSCredentials(accessKey, secretKey);
return (AmazonS3Client)AmazonS3ClientBuilder.standard()
.withRegion(region)
.withCredentials(new AWSStaticCredentialsProvider(awsCredentials))
.build();
}
}
Controller
@ApiOperation(value = "๊ฒ์๊ธ ๋ฑ๋ก", notes = "๊ฒ์๊ธ์ ๋ฑ๋กํฉ๋๋ค.")
@PostMapping("save")
public ResponseEntity<String> create(
@RequestPart(value = "board") BoardRequest request,
@RequestPart(value = "image", required = false) MultipartFile image,
@RequestPart(value = "file", required = false) MultipartFile file){
try {
//s3
String fileName= image.getOriginalFilename();
String fileUrl= "https://" + bucket + "/image" +fileName;
ObjectMetadata metadata= new ObjectMetadata();
metadata.setContentType(file.getContentType());
metadata.setContentLength(file.getSize());
amazonS3Client.putObject(bucket,fileName,file.getInputStream(),metadata);
return ResponseEntity.ok(fileUrl);
} catch (IOException e) {
throw new AppException(ErrorCode.INTERNAL_SERVER_ERROR, "์ ์ฅ์ ์คํจํ์์ต๋๋ค.");
}
}
S3์ ์ด๋ฏธ์ง ํ์ผ์ ์ ๋ก๋ํ๊ณ , ์ ๋ก๋๋ ์ด๋ฏธ์ง์ URL์ ๋ฐํํ๋ ๊ธฐ๋ฅ์ ์ํํ๋ค.
- String fileName = image.getOriginalFilename();
- ์ ๋ก๋ํ ํ์ผ์ ์๋ณธ ์ด๋ฆ์ fileName ๋ณ์์ ์ ์ฅ
- String fileUrl = "https://" + bucket + "/image" + fileName;
- fileUrl ๋ณ์๋ฅผ ์์ฑํ์ฌ ์ ๋ก๋ํ ํ์ผ์ URL์ ๊ตฌ์ฑํ๋ค. ์ฌ๊ธฐ์ bucket์ S3 ๋ฒํท์ ์ด๋ฆ์ด๋ฉฐ, fileName์ ์ ๋ก๋ํ ํ์ผ์ ์ด๋ฆ์ด๋ค. "/image"๋ ํ์ผ์ ์ ์ฅํ S3 ๋ด์ ๊ฒฝ๋ก๋ฅผ ๋ํ๋ธ๋ค. ์ค์ ๋ก S3 ๋ฒํท ๋ด์์๋ ์ด ๊ฒฝ๋ก๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ์ผ์ด ์ ์ฅ๋๋ค.
- ObjectMetadata metadata = new ObjectMetadata();
- ObjectMetadata ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ค. ์ด ๊ฐ์ฒด๋ S3์ ์ ๋ก๋ํ ํ์ผ๊ณผ ๊ด๋ จ๋ ๋ฉํ๋ฐ์ดํฐ ์ ๋ณด๋ฅผ ํฌํจํ๋ค.
- metadata.setContentType(file.getContentType());
- ์ ๋ก๋ํ ํ์ผ์ ์ปจํ ์ธ ํ์ ์ ์ค์ ํ๋ค. file.getContentType() ๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ ์ ๋ก๋ํ ํ์ผ์ MIME ํ์ ์ ๋ฉํ๋ฐ์ดํฐ์ ์ค์ ํ๋ค. ์๋ฅผ ๋ค์ด, ์ด๋ฏธ์ง ํ์ผ์ด๋ฉด image/jpeg ๋๋ image/png์ ๊ฐ์ ๊ฐ์ด ๋๋ค.
- metadata.setContentLength(file.getSize());
- ์ ๋ก๋ํ ํ์ผ์ ํฌ๊ธฐ๋ฅผ ์ค์ ํ๋ค. file.getSize() ๋ฉ์๋๋ฅผ ํธ์ถํ์ฌ ํ์ผ์ ํฌ๊ธฐ(๋ฐ์ดํธ ๋จ์)๋ฅผ ๊ฐ์ ธ์จ ํ, ์ด๋ฅผ ๋ฉํ๋ฐ์ดํฐ์ ์ปจํ ์ธ ๊ธธ์ด๋ก ์ค์ ํ๋ค.
- amazonS3Client.putObject(bucket, fileName, file.getInputStream(), metadata);
- AmazonS3Client์ putObject ๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ ํ์ผ์ S3 ๋ฒํท์ ์ ๋ก๋ํ๋ค. ์ด ๋ฉ์๋๋ ๋ฒํท ์ด๋ฆ(bucket), ์ ์ฅ๋ ํ์ผ ์ด๋ฆ(fileName), ์ ๋ก๋ํ ํ์ผ์ InputStream(์ฌ๊ธฐ์๋ file.getInputStream()), ๊ทธ๋ฆฌ๊ณ ํ์ผ๊ณผ ๊ด๋ จ๋ ๋ฉํ๋ฐ์ดํฐ(metadata)๋ฅผ ๋งค๊ฐ๋ณ์๋ก ๋ฐ๋๋ค.
์ด์ S3 ์์ ํ์ผ์ ๋ค์ด๋ก๋ํ๋ REST API ์๋ํฌ์ธํธ๋ฅผ ๊ตฌํํด๋ณด์
@ApiOperation(value = "์ฒจ๋ถํ์ผ ๋ค์ด๋ก๋", notes = "S3์ ์ ์ฅ๋ ์ฒจ๋ถํ์ผ์ ๋ค์ด๋ก๋ํฉ๋๋ค.")
@GetMapping("/s3/download/{fileName}")
public ResponseEntity<byte[]> download(@PathVariable String fileName) throws IOException {
S3Object o = amazonS3Client.getObject(new GetObjectRequest(bucket, fileName));
S3ObjectInputStream objectInputStream = o.getObjectContent();
byte[] bytes = IOUtils.toByteArray(objectInputStream);
String encodedFileName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
httpHeaders.setContentLength(bytes.length);
httpHeaders.setContentDispositionFormData("attachment", encodedFileName);
// ํ์ผ์ ๋ฐ์ดํธ ๋ฐฐ์ด๊ณผ ์ค์ ๋ HTTP ํค๋๋ฅผ ์ฌ์ฉํ์ฌ ResponseEntity ๊ฐ์ฒด๋ฅผ ์ง์ ๋ฐํํฉ๋๋ค.
return new ResponseEntity<>(bytes, httpHeaders, HttpStatus.OK);
}
- S3Object o = amazonS3Client.getObject(new GetObjectRequest(bucket, fileName));
- S3 ๋ฒํท์์ bucket์ S3 ๋ฒํท์ ์ด๋ฆ, fileName์ ๋ค์ด๋ก๋ํ๋ ค๋ ํ์ผ์ ์ด๋ฆ์ผ๋ก ํ์ผ์ ๊ฐ์ ธ์จ๋ค.
- S3ObjectInputStream objectInputStream = o.getObjectContent();:
- ๊ฐ์ ธ์จ S3 ๊ฐ์ฒด์์ ์ ๋ ฅ ์คํธ๋ฆผ์ ์ป์ด, ํ์ผ์ ๋ฐ์ดํฐ๋ฅผ ์ฝ๋๋ค.
- byte[] bytes = IOUtils.toByteArray(objectInputStream);
- Apache Commons IO ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ IOUtils.toByteArray ๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ ์ ๋ ฅ ์คํธ๋ฆผ์ ํ์ผ์ ์ค์ ๋ฐ์ดํฐ๋ฅผ ๋ด์ ๋ฐ์ดํธ ๋ฐฐ์ด๋ก ๋ณํํ๋ค.
- String encodedFileName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
- ํ์ผ ์ด๋ฆ์ URL ์ธ์ฝ๋ฉํ์ฌ HTTP ํค๋์์ ์์ ํ๊ฒ ์ฌ์ฉํ ์ ์๋๋ก ํ๋ค. ๊ณต๋ฐฑ์ %20์ผ๋ก ๋ณํํ๋ค.
- httpHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
- ์๋ต์ ์ฝํ ์ธ ํ์ ์ ์ด์ง ๋ฐ์ดํฐ(APPLICATION_OCTET_STREAM)๋ก ์ค์ ํ๋ค.
- httpHeaders.setContentLength(bytes.length);
- ์๋ต ๋ณธ๋ฌธ์ ๊ธธ์ด(ํ์ผ ๋ฐ์ดํฐ์ ํฌ๊ธฐ)๋ฅผ ์ค์ ํ๋ค.
- httpHeaders.setContentDispositionFormData("attachment", encodedFileName);
- ๋ธ๋ผ์ฐ์ ๊ฐ ์๋ต์ ํ์ผ ๋ค์ด๋ก๋๋ก ์ฒ๋ฆฌํ๋๋ก Content-Disposition ํค๋๋ฅผ ์ค์ ํฉ๋๋ค. ์ด ๋, ์ธ์ฝ๋ฉ๋ ํ์ผ ์ด๋ฆ์ ์ฌ์ฉํ๋ค.
ํฌ์คํธ๋งจ์์ form-dataํ์์ผ๋ก ํ์ผ์ ์ ๋ก๋ํ๊ณ api๋ฅผ ํธ์ถํด๋ณด์
์ ์ ์ฅ์ด ๋์์ผ๋ฉด S3์ ์ ์ฅ๋ ์ด๋ฏธ์ง URL์ด ๋ฐํ๋๊ณ
S3์๋ ์ ์ ์ฅ์ด ๋๋ค.
๋ค์ด๋ก๋๋ ํด๋ณด์
ํฌ์คํธ๋งจ์ผ๋ก api ๋ฅผ ํธ์ถํ๋ฉด 200์ด ๋จ๊ณ , ์ค์ ์น์์ url๋ฅผ ์ ๋ ฅํ๋ฉด ๋ค์ด์ด ๋ฐ์์ง๋ค.
๋~!!