1. 목적/배경
    1. https://blog.eomsh.com/187 에서 AWS SES를 사용해서 이메일 발송 서비스를 구축했다면 bounce 관리가 필요하다고 간단히만 코멘트
    2. 그런데, 메일함이 꽉 차서 유저에게 메시지를 띄워주거나, 반송되는 이메일 주소에게 본인의 어플리케이션이 메일을 다시 보내지 않게 하려면 메일 주소를 어플리케이션에 DB로 구축해야함
      1. 수신거부 등의 이벤트도 수집 가능
  2. 방법
    1. AWS SES 웹 콘솔에서 Notification 설정 -> SNS로 전송 -> Lambda 호출 -> 작성한 파이썬 코드 호출
      1. 추가 로직이 필요 없으면 SNS에서 바로 http api호출해도 될 것 같기는 함
      2. 참고 링크 
  3. 간단히 작성한 파이썬 소스코드
    1. AWS Lambda runtime 파이썬 레이어에 request 사용을 위해 추가 후 아래 코드 실행되게 함(참고 링크)
    2. 해당 API가 호출되어 저장되는 DB DDL과 Java 어플리케이션도 있는데 그건 추후 로그에 작성해보겠음

 

import json
import os

import requests

AWS_ACCOUNT_ID = "입력필요"  # AWS 계정ID(readable한 키워드-고유의 숫자ID 포맷 사용, 해당 파이썬 코드가 실행되는 AWS계정에 맞춰서 변경하세요)
AWS_REGION_CD = "입력필요"  # AWS 리전 코드(해당 파이썬 코드가 실행되는 lambda 리전에 맞춰서 변경하세요)

REQ_AUTH_KEY = "API호출할 때 보안을 위해서 사용하는 인증키"  # API 호출에 사용하는 인증용 키

# HTTP API로 SES알림 저장을 요청하는 end point URL
SES_NOTIFICATION_SAVE_API_URL = "https://블라블라/" + AWS_ACCOUNT_ID + "/" + AWS_REGION_CD + "/블라블라"

def lambda_handler(event, context):
    ses_notification = event['Records'][0]['Sns']['Message']  # SNS로부터 전달받는 이벤트 추출
    data_json = json.loads(ses_notification)  # json으로 파싱/로드

    notification_type = data_json['notificationType']
    if notification_type == "AmazonSnsSubscriptionSucceeded":
        print(f"AmazonSnsSubscriptionSucceeded는 저장하지 않습니다.(SNS로 구독 성공시 이벤트) data_json: {data_json}")
        return

    # 알림 저장 API 호출
    data_json_str = json.dumps(data_json, ensure_ascii=False).encode('utf-8')
    result_save_api = request_http_save(SES_NOTIFICATION_SAVE_API_URL, 10, data_json_str)
    print(f"result_save_api: {result_save_api}", end="\n\n")


def test_save_from_sample_file():  # 테스트 목적으로 만든 파일의 샘플 json을 읽어서 저장요청

    # 샘플: https://docs.aws.amazon.com/ko_kr/ses/latest/dg/notification-examples.html#notification-examples-bounce 를 파일로 만들어서 테스트
    file_name = "test-bounce-exist-dns-data.json"
    # file_name = "test-bounce-not-exist-dns-data.json"
    # file_name = "test-AmazonSnsSubscriptionSucceeded.json"
    current_directory = os.getcwd()
    test_json_file_path = os.path.join(current_directory, file_name)

    with open(test_json_file_path, 'r', encoding='UTF8') as f:
        data_json = json.load(f)  # 문자열 json을 파싱한 내용

    print(f"테스트 파일 {file_name}의 내용 ===>\n\t", json.dumps(data_json, indent=4, ensure_ascii=False), end="\n\n")

    notification_type = data_json['notificationType']
    if notification_type == "AmazonSnsSubscriptionSucceeded":
        print(f"AmazonSnsSubscriptionSucceeded는 저장하지 않습니다. data_json: {data_json}")
        return

    # 알림 저장 API 호출
    data_json_str = json.dumps(data_json, ensure_ascii=False).encode('utf-8')
    result_save_api = request_http_save(SES_NOTIFICATION_SAVE_API_URL, 10, data_json_str)
    print(f"result_save_api: {result_save_api}", end="\n\n")

def request_http_save(uri: str, timeout_seconds: int, req_json_str: str) -> object:
    """
    외부 API에 SES 알림 데이터 전송하는 HTTP통신용 함수

    :param uri: 요청 URI
    :param timeout_seconds: 타임아웃(초)
    :param req_json_str: 요청할 json(문자가 아니라 json타입)
    :return:
    """
    print("\n === Start request_http_save. uri: " + uri)
    headers = {
        'reqAuthKey': REQ_AUTH_KEY,
        'Content-Type': 'application/json; charset=utf-8'
    }

    try:
        # 한글이 포함되어 있을 수 있음
        # req_json_str = json.dumps(ses_notification_json, ensure_ascii=False).encode('utf-8')
        response = requests.request("POST", uri, headers=headers, timeout=timeout_seconds, data=req_json_str)

        return {
            'statusCode': response.status_code,
            'body': response.text
        }

    except requests.exceptions.Timeout as errd:
        print("Timeout Error : ", errd)

    except requests.exceptions.ConnectionError as errc:
        print("Error Connecting : ", errc)

    except requests.exceptions.HTTPError as errb:
        print("Http Error : ", errb)

    # Any Error except upper exception
    except requests.exceptions.RequestException as erra:
        print("AnyException : ", erra)

# API에 저장요청(로컬 테스트를 위해서 파일에서 샘플 Json을 읽어서 호출)
#test_save_from_sample_file()

 

 

트러블슈팅할때 가끔 RSS메모리를 주기적으로 관찰해야할 필요가 있습니다.

커맨드 등등으로 했었는데 아래 bash쉘로도 가능합니다.

 - 참고: https://poonamparhar.github.io/dynamic_compiler_threads/

#!/bin/bash

pid=$1
smaps_file=/proc/$pid/smaps
output_file=rss_$pid.out
rm rss_$pid.out

echo 'Monitoring RSS of the Process' $pid. Saving output to file $output_file.

while true
do
  # Get the current timestamp
  timestamp=$(date +"%FT%T%z")
  # Get the current RSS value
  rss=$(grep -m 1 "^Rss:" "$smaps_file" | awk '{print $2}')
  # write timestamp and rss to the output file
  echo "$timestamp: $rss" >> "$output_file"
  sleep 5
done

 

위 스크립트 생성 후 ps -ef | grep java 로 PID 확인해서 스크립트 아큐먼트로 PID를 입력해서 사용하면 파일로 저장됩니다.

 

P.S. 링크 글 자체의 퀄리티도 꽤 높습니다. 이런 글 볼때마다 커널이나 OS 공부, C공부를 다시 깊게 해보고 싶다는 생각이 드는군요.

-- mvnd 홈 디렉토리 생성 및 다운로드 후 압축해제
cd ~ && wget https://downloads.apache.org/maven/mvnd/1.0-m6/maven-mvnd-1.0-m6-m39-linux-amd64.tar.gz && tar -xzf maven-mvnd-1.0-m6-m39-linux-amd64.tar.gz && rm -f maven-mvnd-1.0-m6-m39-linux-amd64.tar.gz

-- 심볼릭 링크 추가
ln -s maven-mvnd-1.0-m6-m39-linux-amd64 mvnd

-- path 등록
cd ~ && echo 'export PATH=mvnd/bin:$PATH' >> .bashrc && source .bashrc

-- 버전 확인 및 실행 & 프로세스 확인
mvnd --version
ps -ef | grep mvnd


참고
https://blog.frankel.ch/faster-maven-builds/1/

https://rudaks.tistory.com/entry/mvnd-%EC%82%AC%EC%9A%A9%EB%B2%95

https://www.mastertheboss.com/jboss-frameworks/jboss-maven/introduction-to-maven-daemon-mvnd/

가끔 Jit 컴파일러 튜닝해야하는 경우 참고하는 내용
 - https://man.archlinux.org/man/java-openjdk11.1.en#Advanced_JIT_Compiler_Options

개발을 하다보면 이미지 리사이징이 필요한 경우가 있습니다.

관련해서 라이브러리, 방법 등 시간될때마다 정리해둘 예정입니다.(계속해서 업데이트)

 

  1. golang으로 만든 라이브러리 imagor
    1. https://github.com/cshum/imagor
  2. imageMgick. 10여년정도 전에 시스템 구축할때 사용했었고 써봤던거라서 지금도 빠르게 필요할때 가끔 사용
    1. https://imagemagick.org/index.php
  3. GraphicsMagick
    1. imagemaginck를 fork해서 만듬
    2. http://www.graphicsmagick.org/
  4. AWS를 서비스를 이용해서 리사이징
    1. https://docs.aws.amazon.com/ko_kr/lambda/latest/dg/with-s3-tutorial.html
    2. 클라우드 사용하면서 종종 이용(golang과 람다 조합하면 비용 및 빠른 시작의 이점으로 좋음)
      1. https://netflixtechblog.medium.com/netflix-images-enhanced-with-aws-lambda-9eda989249bf
  5. 대규모 이미지 서비스를 개발한다면 스토리지 비용을 무시할 수 없음. 이때 포맷 선택도 중요한 사항
    1. LINE에서 JPEG->HEIF포맷으로 변환
      1. https://engineering.linecorp.com/ko/blog/antman-project-development-story
      2. 콜드 스토리지를 잘 활용해야함
      3. 사실 이정도를 고려해야하는 서비스는 국내에 많지 않아서 개발 조직 규모/유지보수 용이성 등을 잘 고려해야하고 오버 엔지니어링 안되게 해야함
  6. 오픈소스 이미지 프록시 서버
    1. ImageMagick, GraphicsMagick 등 이미지 변환 작업을 빠르게 해줄 수 있는 단독 서버
    2. 외부에 존재하는 많은 이미지들의 리사이징에 최적
    3. https://github.com/imgproxy/imgproxy
  1. 목적/배경
    1. 서비스를 운영하다보면 봇 등의 공격이 있을 수 있습니다.
    2. 특정 IP를 차단해도 IP를 변경해가면서 공격이 들어옵니다. 이때 효과적인 차단 방법 중 하나입니다.
  2. 제한 사항
    1. AWS의 CloudFront를 사용해야합니다.
    2. CloudFront에서 헤더 추가를 활성화해서 nginx에서 JA3헤더를 얻을 수 있어야합니다.(참고 링크)
  3. 참고
    1. 서버에서 tcpdump -s 0 -A 'tcp dst port 80' 등으로 인입되는 헤더의 CloudFront-Viewer-JA3-Fingerprint 를 분석
      1. AWS JA3헤더 관련 참고 링크
        1. 상세 셋팅 방법은 하단 참고
      2. ja3 fingerprint란? 링크
  4. nginx 설정 방법
    1. 확인된 나쁜 JA3 Fingerprint값을 아래 nginx 설정에 추가해서 1(true)로 설정 -> 403리턴
map $http_cloudfront_viewer_ja3_fingerprint $block_fingerprint {
        default 0;
        "차단할 CloudFront-Viewer-JA3-Fingerprint값" 1;
        "차단할 CloudFront-Viewer-JA3-Fingerprint값" 1;
}

server
{
        if ($block_fingerprint) {
           return 403;
        }

        .......

}

 
추가로, API 요청이었고 rate limit 처리해서 429 리턴 json으로 처리해야하는 경우 아래처럼 작업 가능

error_page 429 /429.json;
location /429.json {
    add_header 'Content-Type' 'application/json charset=UTF-8';
    return 429 '{"success":false, "errorCd":"RATE_LIMITED","msg":"Too Many Requests(rate limit nginx)"}';
}

 


참고- AWS Cloud Front 헤더 셋팅 방법

  1. CloudFront -> Distributions -> 대상 CloudFront -> Edit behavior
    1.  

AWS Cloud Front의 Custom Origin request policy를 추가하는 위치

2. Custom policies 생성

Custom policies 추가하는 방법
샘플 Custom Policy Origin reqeust settings

요즘 가장 관심이 있는 java쪽 기능/피쳐

모든 기존 프로덕트를 재 개발할수도 없고, 리액티브 스타일로 개발시 여러 허들과 트러블 슈팅도 있어서..

java virtual thread의 draft(2023/03/06 created)
https://openjdk.org/jeps/8303683

 

 

노이즈가 좀 있는 내용 같지만 추가 참고 글

 - https://news.hada.io/topic?id=9250

 

 

jjwt를 사용해서 보통 JWT 처리를 하는데 혹시 secretKey 기존에 대충 만들었다면(길이가 짧게) 0.10.0 버전부터 에러가 발생함

 - https://github.com/jwtk/jjwt/issues/334

https://github.com/jwtk/jjwt/blob/6b980553cfb4435ec14b7842af5b529735acbb2d/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java#L364

 

 

이에 간단히 사용하는 SecretKey 생성 소스

import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Encoders;
import io.jsonwebtoken.security.Keys;

import javax.crypto.SecretKey;

public class JwtSecretKeyMaker {

	/**
	 * JJWT secretKey를 생성할때 알고리즘에 맞는 length로 만듬
	 *  - https://github.com/jwtk/jjwt#jws-key-create
	 *  - jjwt 0.10.0 부터는 length가 작으면 에러가 발생함: https://github.com/jwtk/jjwt/issues/334
	 *
	 * @param args
	 */
	public static void main(String[] args) {

		//Creating Safe Keys(length: 256bit))
		SecretKey key = Keys.secretKeyFor(SignatureAlgorithm.HS256); // HS256: HMAC-SHA256
		String secretString = Encoders.BASE64.encode(key.getEncoded());

		System.out.println(secretString);

	}
}

+ Recent posts