asap 프로젝트에 브랜드와 상품 이미지 업로드 기능이 구현되어 있다. 업로드되는 이미지는 S3 버킷에 저장되며 해당 이미지의 경로는 각 entity의 url 필드에 저장되어 클라이언트가 원할 때 이미지를 불러올 수 있다.
하지만 이미지는 화면에서 작은 크기의 썸네일을 보여주는 경우가 존재한다. 이때 큰 사이즈의 원본 이미지 파일을 계속하여 요청하는 것은 전송 효율이 떨어지며 요청이 늘어날수록 서비스의 응답속도는 느려진다.
S3에 이미지를 업로드 시 원본 이외에 리사이징된 이미지를 함께 저장하면 작은 크기의 이미지가 필요할 때 리사이징된 이미지를 요청하여 전송 효율을 높이는 게 가능하다.
AWS에는 Lambda 컴퓨팅서비스를 제공한다. 이는 개발자가 함수를 만들어 이를 저장하면 이벤트에 응답하여 함수를 실행해 준다.
Lambda의 장점은 서버리스다. 이미지 리사이징에 관련된 작업을 서버가 아닌 AWS Lambda를 통해 처리하게 되면 서버에 대한 걱정 없이 비즈니스 개발에만 집중할 수 있다.
S3 버킷 생성과 이미지 업로드 방법은 생략
원본 이미지를 저장하는 버킷 외에 리사이징된 이미지를 저장하는 버킷을 생성한다.
(aws 예제에서는 버킷을 분리하는 것을 권장하고 있다. 하지만 버킷을 분리하지 않고도 prefix를 설정해 1개의 버킷안에 폴더를 나누어 용도에 맞는 경로로 이미지 업로드도 가능하다)
정책 설정
생성한 버킷에 접근 권한을 포함하는 정책을 IAM에서 생성한다. 로그 확인을 위해 CloudWatch 관련 정책과 s3관련 정책을 추가한다.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogStream",
"logs:CreateLogGroup",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:*:*:*"
},
{
"Effect": "Allow",
"Action": [
"s3:GetObject"
],
"Resource": "arn:aws:s3:::{버킷 이름}/*"
},
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:PutObjectAcl"
],
"Resource": "arn:aws:s3:::{버킷 이름}/*"
}
]
}
3.역할 생성
AWS 리소스에 엑세스 할 수 있는 권할을 제공하는 역할을 생성한다.
AWS 서비스를 선택하고 서비스 및 사용 사례를 Lambda로 선택한다.
그 후 권한 추가에서 위에서 만든 정책을 연결한다.
4.람다 생성
람다에서 함수 생성을 새로 작성한다.
함수를 생성할 언어를 선택하고 실행 역할을 기존 역할 사용을 선택하여 위에서 만든 역할을 연결한다.
함수가 동작하는데 시간이 소요될 수 있으므로, 제한 시간을 50초로 설정한다.
implementation group: 'com.amazonaws', name: 'aws-lambda-java-core', version: '1.2.1'
implementation group: 'com.amazonaws', name: 'aws-lambda-java-events', version: '3.7.0'
implementation group: 'com.amazonaws', name: 'aws-java-sdk', version: '1.11.969'
test {
useJUnitPlatform()
}
task buildZip(type: Zip) {
from compileJava
from processResources
into('lib') {
from(configurations.runtimeClasspath)
}
}
build.dependsOn buildZip
public class LambdaHandler implements RequestHandler<S3Event, String> {
private static final float MAX_HEIGHT = 60;
private final String JPG_TYPE = (String) "jpg";
private final String JPG_MIME = (String) "image/jpeg";
private final String JPEG_TYPE = (String) "jpeg";
private final String JPEG_MIME = (String) "image/jpeg";
private final String PNG_TYPE = (String) "png";
private final String PNG_MIME = (String) "image/png";
private final String GIF_TYPE = (String) "gif";
private final String GIF_MIME = (String) "image/gif";
public String handleRequest(S3Event s3Event, Context context) {
LambdaLogger logger = context.getLogger();
try {
S3EventNotificationRecord record = s3Event.getRecords().get(0);
String srcBucket = record.getS3().getBucket().getName();
String srcKey = record.getS3().getObject().getUrlDecodedKey();
String dstBucket = "{버킷 네임}";
Matcher matcher = Pattern.compile(".*\\.([^\\.]*)").matcher(srcKey);
if (!matcher.matches()) {
logger.log("Unable to infer image type for key " + srcKey);
return "";
}
String imageType = matcher.group(1);
if (!(JPG_TYPE.equals(imageType)) && !(PNG_TYPE.equals(imageType))
&& !(JPEG_TYPE.equals(imageType)) && !(GIF_TYPE.equals(imageType))) {
logger.log("Skipping non-image " + srcKey);
return "";
}
AmazonS3 s3Client = AmazonS3ClientBuilder.defaultClient();
S3Object s3Object = s3Client.getObject(new GetObjectRequest(
srcBucket, srcKey));
InputStream s3ObjectData = s3Object.getObjectContent();
BufferedImage srcImage = ImageIO.read(s3ObjectData);
int srcHeight = srcImage.getHeight();
int srcWidth = srcImage.getWidth();
int width = (int) (srcWidth * (MAX_HEIGHT / srcHeight));
int height = (int) MAX_HEIGHT;
BufferedImage resizedImage = new BufferedImage(width, height,
BufferedImage.TYPE_INT_RGB);
Graphics2D g = resizedImage.createGraphics();
g.setPaint(Color.white);
g.fillRect(0, 0, width, height);
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g.drawImage(srcImage, 0, 0, width, height, null);
g.dispose();
ByteArrayOutputStream os = new ByteArrayOutputStream();
ImageIO.write(resizedImage, imageType, os);
InputStream is = new ByteArrayInputStream(os.toByteArray());
ObjectMetadata meta = new ObjectMetadata();
meta.setContentLength(os.size());
if (JPG_TYPE.equals(imageType)) {
meta.setContentType(JPG_MIME);
}
if (JPEG_TYPE.equals(imageType)) {
meta.setContentType(JPEG_MIME);
}
if (PNG_TYPE.equals(imageType)) {
meta.setContentType(PNG_MIME);
}
if (GIF_TYPE.equals(imageType)) {
meta.setContentType(GIF_MIME);
}
logger.log("Writing to: " + dstBucket + "/" + srcKey);
try {
s3Client.putObject(new PutObjectRequest(dstBucket, srcKey, is, meta).withCannedAcl(
CannedAccessControlList.PublicRead));
} catch (AmazonServiceException e) {
logger.log(e.getErrorMessage());
System.exit(1);
}
logger.log("Successfully resized " + srcBucket + "/"
+ srcKey + " and uploaded to " + dstBucket + "/" + srcKey);
return "Ok";
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
위 프로젝트를 빌드하면.zip파일이 생성된다.
위에 처럼 업로드후 url을 복사한다.
코드 소스에서 S3업로드 방식을 선택하여 복사한 url을 넣어주고 런타임 설정을 생성한 함수 프로젝트에 맞춰서 핸들러를 기입한다.
리사이징 Before
리사이징 After
ref
AWS S3에 이미지 업로드하고 AWS Lambda로 이미지 리사이징 적용하기
AWS Lambda Image Resize 도입기
AWS S3 트리거
Lambda 예제코드