티스토리 뷰

회사 업무 중 채팅 서버를 구현 중이었는데요... 의도치 않은 방향성의 전환으로(?) AWS API Gateway 에서 제공하는 WebSocket 기능을 사용하게 되었습니다.

 

채팅 서버의 스케일 아웃 시 여러 서버 간 메시지 동기화를 위해 레디스나 카프카 사용을 고민했었는데요 프로젝트 사정상 API Gateway가 제공하는 WebSocket 기능을 사용하여 소캣 기능을 구현하도록 하였습니다. 이렇게 하면 별도 Application 서버는 웹 소캣 기능을 가지지 않아도 되고 별도의 커넥션 관리 및 메시지 동기화 작업이 필요하지 않다고 합니다...! 

 

우선 API Gateway 의 웹 소켓 API를 그림으로 살펴보면 다음과 같습니다.

 

추가적으로 HTTP REST API 들이 늘어나는 환경에서도 테스트를 진행할 예정입니다. (우선 지금은 간단하게 EC2 하나로만 구성했습니다.)

 

먼저 SpringBoot 프로젝트를 띄우고 EC2에 배포하도록 하겠습니다. 포트는 8082로 열려있습니다. EC2 보안 그룹 인바운드 규칙도 우선 8082로 들어오는 모든 요청을 열어두도록 하겠습니다...! (보안상 안좋으니 테스트 후 다시 수정하겠습니다...!)

 

기본적인 RestController 는 다음과 같습니다.

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
public class CommonRestController {

    @PostMapping("/connect")
    public ResponseEntity<?> connect(@RequestBody Object request) {
        log.info("connection success");
        log.info(request.toString());
        return ResponseEntity.ok("Connect OK");
    }

    @GetMapping("/")
    public ResponseEntity<?> any() {
        log.info("call any");
        return ResponseEntity.ok("Any OK");
    }

    @PostMapping("/disconnect")
    public ResponseEntity<?> disconnect(@RequestBody Object request) {
        log.info("disconnection success");
        log.info(request.toString());
        return ResponseEntity.ok("Disconnect OK");
    }
}

어떤 형태로 데이터가 올지 몰라 우선 Object 형태로 받았지만 Dto를 생성하는 편이 좋다고 생각합니다...!

 

이제 API Gateway를 WebSocket 형태로 생성해보도록 하겠습니다.

 

1. WebSocket Api 를 생성합니다.

 

2. API 세부 정보를 지정합니다.

 

여기서 라우팅 선택 표현식이 중요한데 클라이언트에서 소켓에 이벤트를 전송할 때 request.body.action 이라는 값으로 보내면 그 값을 가지고 라우팅 한다는 의미입니다.

 

3. 경로 추가 및 통합 연결

이 부분은 우선 넘어가도 되고 기본적으로 $connect, $disconnect, $default 만 생성한 뒤 통합 연결을 우선 mock 으로 연결해도 됩니다.

 

4. 스테이지 추가

이 과정은 환경의 경로를 선택하는 것으로 dev 로 지정했습니다. dev, test, production 등 원하는 이름을 넣으시면 됩니다.

 

이렇게 하고 나면 WebSocket 용 API Gateway가 생성됩니다.

 

기본적으로 websocket에 필요한 라우트 키가 같이 생성됩니다. (안 보인다면 경로 생성을 통해 생성하면 됩니다.)

 

1. $connect - 기본적으로 클라이언트가 websocket API에 연결될때 발생하는 이벤트입니다.

2. $disconnect - 클라이언트가 websocket API와의 연결이 종료될때 발생하는 이벤트입니다.

3. $default - 특정 라우트키 값이나 매칭되는 값이 없을때 기본적으로 호출되는 이벤트입니다.

 

먼저 모든 이벤트에 대해 Mock을 추가해서 연결을 확인해보겠습니다. 아래와 같이 통합 요청 탭에서 Mock을 추가합니다.

 

통합 요청에서 Mock으로 설정합니다.
Mock을 선택합니다.
요청 템플릿 편집을 눌러서 아래와 같이 템플릿 표현식을 추가합니다.
\$default 템플릿 표현식 추가
템플릿 생성을 눌러 위와 같이 템플릿을 생성합니다.

 

이후 통합 요청 템플릿을 다음과 같이 작성합니다.

{
    "statusCode": 200,
    "id" : "$context.connectionId",
    "domain" : "$context.domainName",
    "stage" : "$context.stage"
}

 

❗️Mock 연결 시 통합 요청에 statusCode 가 포함되어 있어야 합니다...!!! 그렇지 않으면 정상적인 응답이 보이지 않습니다...! 가능하다면 꼭 CloudWatch 로그 설정을 하여 직접 API Gateway의 로그를 확인하시기 바랍니다!!!

 

통합 요청 템플릿을 적용하지 않는 경우 연결은 잘 되겠지만 응답에서 500 에러 등이 발생할 수 있습니다...!

 

이후 wscat을 통해 접속해보면 다음과 같이 API Gateway에 웹소켓 접속이 가능해집니다.

 

접속 주소는 API Gateway console에서 스테이지 탭을 확인하시면 알 수 있습니다.

 

이제 실제로 API 서버로 통신하는 것을 구현해보겠습니다. 아래와 같이 통합 요청 부분을 수정해줍니다.

 

$connect 경로 통합 요청 수정
$disconnect 경로 통합 요청 수정

$connect와 $disconnect의 통합 요청을 변경해주면 됩니다.

통합 유형을 HTTP POST 방식으로 변경하고 (spring 코드에서 PostMapping으로 받고 있기 때문에) 엔드포인트를 EC2 퍼블릭 ip (탄력적 ip)로 지정하고 HTTP 경로를 적어줍니다.

통합 요청을 사용하지 않는 경우 클라이언트의 요청이 바로 API 서버로 전달됩니다. (저는 그 전에 데이터를 가공하여 전달하기 위해 통합 요청을 사용하였습니다.)

 

이제 다시 $connect, $disconnect를 하게 되면 API 서버에 로그가 찍히는 것을 확인할 수 있습니다.

 

포스트맨 실행 결과
서버 실행 결과


지금까지 AWS API Gateway를 WebSocket 기능으로 생성하고 connect 하는 방법을 알아보았습니다. 다음 글에서는 실제 커스텀한 이벤트 경로로 요청을 보내고 AWS API Gateway를 통해 다시 같은 connectionId를 가진 소켓 클라이언트에게 메시지를 전달하는 방법에 대해 알아보겠습니다.

참고

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
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 29 30 31
글 보관함