티스토리 뷰

이전 글에서는 AWS API Gateway를 WebSocket 형태로 생성하고 기본적인 connect, disconnect 하는 방법을 알아보았습니다. 이제 새로운 커스텀 경로를 생성하여 데이터를 전송하고 그 결과 다시 Spring Application에서 API Gateway 쪽으로 전송해 연결된 소켓 클라이언트에 데이터를 전달해보도록 하겠습니다.

 

먼저 AWS API Gateway에 새로운 경로를 추가하고 통합 요청 설정을 해주겠습니다. 이는 기존에 추가한 connect, disconnect 경로와 유사합니다. 

 

이렇게 경로를 생성한 뒤, 요청 템플릿을 추가해줍니다.

{
    "connectionId" : "$context.connectionId",
    "requestId": "$input.path('requestId')",
    "body" : $input.json('$.body')
}

// 실제 POST 요청 시 다음과 같이 request body를 보내면 됩니다.
{
    "action": "echo",
    "requestId": "YOUR_UUID_아무거나",
    "body": {
        "key": "12345",
        "message": "hello~~!!!"
    }
}

이제 해당 경로를 호출하면 요청 데이터의 body를 그대로 다시 API Gateway 쪽으로 전송하는 방법을 알아보겠습니다. 데이터를 전송하기 위한 URL은 다음에서 확인할 수 있습니다.

 

 

해당 URL을 다음과 같이 application.yml에 설정합니다. 단, @connections 부분은 제거합니다. 그리고 나서 ApiGateway 설정을 합니다.

cloud:
  aws:
    region: ap-northeast-2
    websocket-url: https://xxxxx.execute-api.ap-northeast-2.amazonaws.com/develop/
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.profile.ProfileCredentialsProvider;
import com.amazonaws.client.builder.AwsClientBuilder;
import com.amazonaws.services.apigatewaymanagementapi.AmazonApiGatewayManagementApi;
import com.amazonaws.services.apigatewaymanagementapi.AmazonApiGatewayManagementApiClientBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AwsApiGatewayConfig {

    @Value("${cloud.aws.region}")
    private String region;

    @Value("${cloud.aws.websocket-url}")
    private String webSocketUrl;

    @Bean
    public AmazonApiGatewayManagementApi awsApiGatewayManagementApi() {
        // WebSocket 백엔드 메세지 전송 URL 설정 정보 (* 연결 URL은 @Connections 경로는 제외)
        AwsClientBuilder.EndpointConfiguration config = new AwsClientBuilder
                .EndpointConfiguration(webSocketUrl, region);
                
        return AmazonApiGatewayManagementApiClientBuilder.standard()
                .withEndpointConfiguration(config)
                .build();
    }
}

 

이후 다음과 같이 AmazonApiGatewayManagementApi를 주입 받아 postToConnection 메서드를 호출하면 됩니다.

@Slf4j
@Component
@RequiredArgsConstructor
public class AwsWsApiGateway {
    private final AmazonApiGatewayManagementApi client;

    public void send(String connectionId, PostToConnectionData<?> data) {
        PostToConnectionRequest wssRequest = new PostToConnectionRequest();
        wssRequest.setConnectionId(connectionId);

        // 전송 대상에게 보낼 메세지 생성
        Charset charset = StandardCharsets.UTF_8;
        CharsetEncoder encoder = charset.newEncoder();
        ByteBuffer buff = null;
        try {
            buff = encoder.encode(CharBuffer.wrap(ObjectMapperUtil.writeValueAsString(data)));
        } catch (CharacterCodingException e) {
            log.error("[AwsWsApiGateway.send] encode error={}", e.getMessage(), e);
        }

        log.debug("[AwsWsApiGateway.send] connectionId = {} | data = {}", connectionId, data);

        wssRequest.setData(buff);
        client.postToConnection(wssRequest);
    }
}
@Slf4j
@RestController
@RequiredArgsConstructor
public class SampleRestController {
    private final AwsWsApiGateway apiGateway;

    @PostMapping("/echo")
    public ResponseEntity<?> echo(@RequestBody CommonWsRequest<?> request) {
        String connectionId = request.connectionId();
        apiGateway.send(connectionId, PostToConnectionData.builder().action("echo").body(request.body()).build());
        return createSuccessResponse(request.requestId(), request.body());
    }
}


// CommonWsRequest.java
public record CommonWsRequest<T>(
        String connectionId,
        String requestId,
        T body
) {
}

// PostToConnectionData.java
public record PostToConnectionData<T> (
        String action,
        T body
) {
    @Builder
    public PostToConnectionData {}
}

이제 결과를 확인해보면 다음과 같습니다.

 

 

❗️추가로 AWS ApiGateway 에서는 한번 연결된 소캣에 아무런 요청, 응답이 없는 경우 기본적으로 10분 뒤 연결이 종료됩니다. 따라서 이렇게 된 경우 연결이 종료된 connectionId에 postToConnection 메시지를 보내는 경우 GoneException 이 발생할 수 있습니다. 저는 try-catch 로 이 에러를 잡아서 redis에 저장했던 connectionId를 삭제하는 기능을 추가로 구현하였습니다.

@Slf4j
@Component
@RequiredArgsConstructor
public class AwsWsApiGateway {
    private final AmazonApiGatewayManagementApi client;
    private final WebSocketService sessionService;

    public void send(String connectionId, PostToConnectionData<?> data) {
        ...
        
        try {
            client.postToConnection(wssRequest);
        } catch (GoneException e) {
            log.error("[AwsWsApiGateway.send] connectionId={} GoneException => ", connectionId, e);
            sessionService.disconnect(connectionId);  // 해당 메서드에서 redis session 삭제
        }
    }
}

참고

이전 글

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
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
글 보관함