[AWS] API Gateway: Websocket API

์„ธ์ƒ์ด ์ฐธ ์ข‹์•„์ ธ์„œ ๊ตณ์ด ์„œ๋ฒ„๋ฅผ ๊ตฌ์„ฑํ•˜์ง€ ์•Š๊ณ ๋„ ์„œ๋ฒ„๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋Š” ์‹œ๋Œ€๊ฐ€ ์™”๋‹ค.
Lambda ๊ฐ™์€ ์„œ๋น„์Šค๋“ค ๋ง์ด๋‹ค.

Lambda์˜ ๊ฐ€์žฅ ํฐ ํŠน์ง•์€ ๋ฌด์ƒํƒœ์„ฑ์ด๋‹ค. ๊ทธ๋ƒฅ ๊ทธ๋•Œ๊ทธ๋•Œ ๋ญ”์ง€๋ชจ๋ฅผ ๋จธ์‹ ์—์„œ ํ”„๋กœ์„ธ์Šค๋ฅผ ์˜ฌ๋ฆฌ๋Š” ๊ฒƒ์ด๋ผ, ๋ณดํ†ต ๊ธฐ์กด ์„œ๋ฒ„์—์„œ ๋งํ•˜๋Š” ์„ธ์…˜์ด๋‚˜ ๋กฑ ํด๋ง ๊ฐ™์€ ์ฒ˜๋ฆฌ๊ฐ€ ์–ด๋ ต๋‹ค. ์›น์†Œ์ผ“๋„ ๊ทธ๋ ‡๊ณ  ๋ง์ด๋‹ค.

ํ•˜์ง€๋งŒ ๋‹คํ–‰ํžˆ๋„ Lambda๊ฐ€ ์•„๋‹ˆ๋ผ API Gateway๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์–ด๋А์ •๋„ ๊ตฌํ˜„์ด ๊ฐ€๋Šฅํ•˜๋‹ค.




๋น„์šฉ

์ด๊ฑด ์ผ๋ฐ˜ API Gateway์™€ ๋‹ค๋ฅด๊ฒŒ ๋น„์šฉ์ด ๋ถ€๊ณผ๋œ๋‹ค.

์š”์ฒญ ์ˆ˜์™€ ์—ฐ๊ฒฐ์‹œ๊ฐ„ ๊ธฐ์ค€์ด๋‹ค.




์˜ˆ์ œ: ๊ฐ„๋‹จํ•œ ์ฑ„ํŒ…์„œ๋ฒ„ ๊ตฌํ˜„ํ•ด๋ณด๊ธฐ

WebSocket API๋ฅผ ํ†ตํ•ด ์ •๋ง ๊ฐ„๋‹จํ•œ ๊ธฐ๋Šฅ๋งŒ ์žˆ๋Š” ๋‹จ์ผ ์ฑ„ํŒ…๋ฐฉ์„ ๊ตฌํ˜„ํ•ด๋ณด๋ฉด์„œ ๊ตฌ์กฐ๋ฅผ ์ตํ˜€๋ณด๋„๋ก ํ•˜๊ฒ ๋‹ค.




Lambda ์ƒ์„ฑ

๋จผ์ € ์„œ๋ฒ„ ๋™์ž‘์„ ๊ตฌํ˜„ํ•  ๋žŒ๋‹ค ํ•จ์ˆ˜๋“ค์„ ์ƒ์„ฑํ•œ๋‹ค.
ํ•˜๋‚˜๋งŒ ๋งŒ๋“ค์–ด์„œ ์—ฐ๊ฒฐํ•ด๋„ ๋˜๊ธด ๋˜๋Š”๋ฐ, ๋‚˜๋Š” ์›น์†Œ์ผ“ ์ ‘์†, ์ ‘์†์ข…๋ฃŒ, ๊ธฐํƒ€๋™์ž‘์— ๋Œ€ํ•ด์„œ ๋ณ„๋„ ํ•จ์ˆ˜๋ฅผ ๋‹ค ๊ตฌํ˜„ํ–ˆ๋‹ค.
๊ทธ๋ž˜์„œ ๋จผ์ € ์šฉ๋„๋ณ„๋กœ ํ•˜๋‚˜์”ฉ ๊น”์•„์คฌ๋‹ค.




WebSocket API ์ƒ์„ฑ

์ด๊ฑด API Gateway ์ฝ˜์†”์— ๊ฐ€์„œ ๋งŒ๋“ค์–ด์•ผ ํ•œ๋‹ค.

์ด๋ฆ„๊ณผ ํ‘œํ˜„์‹์„ ์ง€์ •ํ•œ๋‹ค.

๋ผ์šฐํŒ… ์„ ํƒ ํ‘œํ˜„์‹์€, ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋ฐ์ดํ„ฐ์— action ๊ฐ’์„ ์–ด๋””๋กœ ๋ณด๋‚ผ ๊ฒƒ์ธ์ง€๋ฅผ ์ง€์ •ํ•œ๋‹ค.
action ๊ฐ’์€ ์†Œ์ผ“ ์—ฐ๊ฒฐ์‹œ์—๋Š” connect, ์ข…๋ฃŒ์‹œ์—๋Š” disconnect๋ฅผ ๋ฐ›๊ณ , ํด๋ผ์ด์–ธํŠธ์—์„œ ์ž„์˜๋กœ action ๊ฐ’์„ ์ง€์ •ํ–ˆ์„ ๋•Œ๋„ ๊ทธ๊ฒƒ๋Œ€๋กœ ๋ฐ›๋Š”๋‹ค.

๊ทธ๋ƒฅ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ์ค˜๋„ ๋ฌด๋ฐฉํ•˜๋‹ค.


๊ทธ ๋‹ค์Œ์—๋Š” ๊ฒฝ๋กœ๋ฅผ ์ง€์ •ํ•ด์ค€๋‹ค.
์•„๊นŒ ๋งŒ๋“  ํ•จ์ˆ˜๋ฅผ ์—ฎ์–ด์ฃผ๋ฉด ๋œ๋‹ค.


๊ทธ๋Ÿผ ์ด๋Ÿฐ์‹์œผ๋กœ ๋งŒ๋“ค์–ด์งˆ ๊ฒƒ์ด๋‹ค.




Dynamo ์ปค๋„ฅ์…˜ ํ…Œ์ด๋ธ” ์ƒ์„ฑ

๊ทธ๋ƒฅ ์„œ๋ฒ„๋ฅผ ์“ฐ๋ฉด ์ด๋ ‡๊ฒŒ๊นŒ์ง€ ํ•˜์ง€๋Š” ์•Š์•„๋„ ๋˜์ง€๋งŒ, Lambda๋Š” ์„œ๋ฒ„๋ฆฌ์Šค์ด๊ธฐ ๋•Œ๋ฌธ์— ์ปค๋„ฅ์…˜์„ ์ €์žฅํ•˜๊ณ  ๊ด€๋ฆฌํ•  ํ•„์š”๊ฐ€ ์žˆ๋‹ค. ์ปค๋„ฅ์…˜์„ ๋“ค๊ณ ์žˆ์–ด์•ผ ๋‹ค์‹œ ๋ธŒ๋กœ๋“œ์บ์ŠคํŠธ๋ฅผ ํ•ด์ค„ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.
๋Œ€์ถฉ ํ•˜๋‚˜ ๋งŒ๋“ค์–ด์ค€๋‹ค.

๋‚ด ์ฑ„ํŒ…์„œ๋ฒ„๋Š” ์ฑ„ํŒ…์ด ํœ˜๋ฐœ์„ฑ์ด๋ผ ๋ณด๋‚ด์ค„ ์ปค๋„ฅ์…˜ ๋ชฉ๋ก๋งŒ ์ €์žฅํ•˜๊ณ  ์ฑ„ํŒ…์šฉ ํ…Œ์ด๋ธ”์€ ๋‘์ง€ ์•Š๋Š”๋‹ค.




์—ฐ๊ฒฐ์‹œ ๋“ค์–ด์˜ค๋Š” event ๊ฐ’

๋žŒ๋‹คํ•จ์ˆ˜์— ์ฝ˜์†”๋กœ๊ทธ๋ฅผ ์ฐ์–ด๋ณธ๋‹ค๋ฉด

connect๋Š” ์ด๋ ‡๊ฒŒ

disconnect๋Š” ์ด๋ ‡๊ฒŒ

๊ธฐํƒ€ default๋Š” ์ด๋ ‡๊ฒŒ ๋“ค์–ด์˜จ๋‹ค.

์—ฌ๊ธฐ์„œ ์ด์ œ connect ํ•จ์ˆ˜์—์„œ๋Š” connectionId๋ฅผ Dynamo์— ์ €์žฅํ•˜๊ณ ,
disconnect์—์„œ๋Š” connectionId๋ฅผ ์ œ๊ฑฐ.
default์—์„œ send action์œผ๋กœ ๋ฉ”์„ธ์ง€๋ฅผ ๋ฐ›์œผ๋ฉด connectionId๋ฅผ ์ „์ฒด์กฐํšŒํ•ด์„œ ๋ชจ๋“  ์ ‘์†์ž๋“ค์—๊ฒŒ ๋ฉ”์„ธ์ง€๋ฅผ ๋ฟŒ๋ ค์ค„ ๊ฒƒ์ด๋‹ค.

connect ํ•จ์ˆ˜ ์ฝ”๋“œ๋Š” ์ด๋ ‡๋‹ค.

const aws = require('aws-sdk');
const client = new aws.DynamoDB.DocumentClient();

exports.handler = async (event) => {
    console.log(event);

    const connection_id = event.requestContext?.connectionId;

    await client.put({ TableName: 'chat_connection', Item: { connection_id } }).promise();

    // TODO implement
    const response = {
        statusCode: 200,
        body: JSON.stringify('Hello from Lambda!'),
    };
    return response;
};

๊ฐ„๋‹จํ•˜๋‹ค.
๊ทธ๋ƒฅ ๋„ฃ๊ธฐ๋งŒ ํ•œ๋‹ค.

disconnect ํ•จ์ˆ˜ ์ฝ”๋“œ๋‹ค.

const aws = require('aws-sdk');
const client = new aws.DynamoDB.DocumentClient();

exports.handler = async (event) => {
    console.log(event);

    const connection_id = event.requestContext?.connectionId;

    await client.delete({ TableName: 'chat_connection', Key: { connection_id } }).promise();

    // TODO implement
    const response = {
        statusCode: 200,
        body: JSON.stringify('Hello from Lambda!'),
    };
    return response;
};

์ด๊ฒƒ๋„ ๊ฐ„๋‹จํ•˜๋‹ค.
connection์ด ๋Š๊ธฐ๋ฉด ์ง€์šฐ๊ธฐ๋งŒ ํ•œ๋‹ค.

๋‹ค์Œ์€ default ์ฝ”๋“œ๋‹ค.

const aws = require('aws-sdk');
const client = new aws.DynamoDB.DocumentClient();
const apiGateway = new aws.ApiGatewayManagementApi({
    endpoint: 'q4w54cu53i.execute-api.ap-northeast-2.amazonaws.com/production'
});

exports.handler = async (event) => {
    console.log(event);

    const body = JSON.parse(event.body);

    switch (body.action) {
        case 'send': {
            const list = (await client.scan({ TableName: 'chat_connection' }).promise()).Items;

            await Promise.all(list.map(async e=>{
                await apiGateway.postToConnection({
                    ConnectionId: e.connection_id, 
                    Data: Buffer.from(JSON.stringify({ action:'receive', name: body.name, message: body.message }))
                }).promise();
            }));
        }
    }

<br>

    // TODO implement
    const response = {
        statusCode: 200,
        body: JSON.stringify('Hello from Lambda!'),
    };
    return response;
};

send action์„ ๋‚ ๋ฆฌ๋ฉด ์ €์žฅ๋˜์–ด์žˆ๋˜ connectionId๋ฅผ ์ „๋ถ€ ์ฝ์–ด์„œ ๋‹ค์‹œ ์ด์ค€๋‹ค.




ํ”„๋ก ํŠธ์—”๋“œ (React)

ํ”„๋ก ํŠธ์—”๋“œ ์ฝ”๋“œ๋Š” ์ด๋Ÿฐ์‹์œผ๋กœ ์ž‘์„ฑํ–ˆ๋‹ค.
์—ฌ๊ธฐ์„œ๋Š” ํ‘œ์ค€ ์›น์†Œ์ผ“์„์ผ๋Š”๋ฐ, socketio ์›น์†Œ์ผ“๋ชจ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด๋„ ๋ฌด๋ฐฉํ•  ๊ฒƒ์ด๋‹ค.

open์ด connect ์„ฑ๊ณต, close๊ฐ€ disconnect์— ํŠธ๋ฆฌ๊ฑฐ๋œ๋‹ค.
๊ทธ๋ฆฌ๊ณ  ๋‚˜๋จธ์ง€ ์ž„์˜์˜ ๋ฉ”์„ธ์ง€๋“ค์€ ์ „๋ถ€ message ์ด๋ฒคํŠธ๋กœ ๋“ค์–ด์˜จ๋‹ค.
์—ฌ๊ธฐ์„œ๋Š” ๋ฉ”์„ธ์ง€๊ฐ€ ์˜ค๋ฉด ์ฑ„ํŒ… ๋ฆฌ์ŠคํŠธ์— ์ด์–ด๋ถ™์—ฌ์„œ ๋ณด์—ฌ์ฃผ๊ฒŒ ํ–ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์ „์†ก์„ ๋ˆ„๋ฅด๋ฉด ๋ฉ”์„ธ์ง€๋ฅผ ๋‚ ๋ฆฌ๋„๋ก ํ–ˆ๋‹ค.

์ „์ฒด ์ฝ”๋“œ๋Š” github์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.
https://github.com/myyrakle/aws-serverless-chat
๊ทธ๋Œ€๋กœ ๋Œ๋ ค๋ด๋„ ๋œ๋‹ค.

์ด๋ ‡๊ฒŒ ํ•ด์„œ ์‹คํ–‰ํ•ด๋ณด๋ฉด

connectionId๋„ ์ž˜ ์Œ“์ผ ๊ฒƒ์ด๊ณ 


์ž˜ ๋Œ์•„๊ฐˆ ๊ฒƒ์ด๋‹ค.




์œ ์˜์‚ฌํ•ญ

๋‚˜๋Š” ๊ฐ„๋‹จํ•œ ํ…Œ์ŠคํŠธ์™€ ์ €๋ ดํ•œ ๋น„์šฉ์„ ์œ„ํ•ด DynamoDB๋ฅผ ์ผ์ง€๋งŒ, ์‹ค์ œ๋กœ ํ™œ๋ฐœํ•œ ์ฑ„ํŒ… ์„œ๋ฒ„ ๋“ฑ์„ ์šด์˜ํ•˜๋ ค๋ฉด Redis๋ฅผ ์“ฐ๋Š” ํŽธ์ด ๋”์šฑ ์ข‹์„ ๊ฒƒ์ด๋‹ค.

๊ทธ๋ฆฌ๊ณ  WebSocket API๋„ ์ œ์•ฝ์‚ฌํ•ญ์ด ์ œ๋ฒ• ์žˆ๋‹ค.
10๋ถ„ ๋™์•ˆ ์š”์ฒญ์ด ์—†๋Š” ์ปค๋„ฅ์…˜์€ ๊ฐ•์ œ๋กœ ๋‹ซ๊ณ , ์š”์ฒญ์„ ์—ด์‹ฌ์‹œ ์˜๊ณ  ์žˆ์–ด๋„ 2์‹œ๊ฐ„์ด ๋„˜์œผ๋ฉด ๊ฐ•์ œ๋กœ ๋‹ซ์•„๋ฒ„๋ฆฐ๋‹ค.
์ด๊ฑฐ ๋ง๊ณ ๋„ ๋ฉ”์„ธ์ง€ ํฌ๊ธฐ์—๋„ ์ œํ•œ์ด ์žˆ๊ณ .. ์ด๊ฒƒ์ €๊ฒƒ ๋งŽ๋‹ค.
์ž์„ธํ•œ๊ฑด ํ•ด๋‹น ๋ฌธ์„œ๋ฅผ ์ฐธ์กฐํ•ด๋ด„์ด ์ข‹๊ฒ ๋‹ค.
https://docs.aws.amazon.com/apigateway/latest/developerguide/limits.html

์ด๋Ÿฐ์ €๋Ÿฐ ์ œ์•ฝ์‚ฌํ•ญ์ด๋‚˜ ๋ถˆํŽธํ•œ ์ ๋“ค์ด ์žˆ๊ธด ํ•˜์ง€๋งŒ, ๊ทธ๊ฑธ ํฌ๊ฒŒ ์‹ ๊ฒฝ์“ธ ์ •๋„๊ฐ€ ์•„๋‹ˆ๋ผ๋ฉด ์™„์ „๊ด€๋ฆฌํ˜•์ด๋ž€ ์ ์—์„œ ์“ธ๋งŒํ•œ ์ด์œ ๋Š” ์ถฉ๋ถ„ํžˆ ์žˆ๋Š” ๊ฒƒ ๊ฐ™๋‹ค.



์ฐธ์กฐ
https://docs.aws.amazon.com/ko_kr/apigateway/latest/developerguide/apigateway-websocket-api.html
https://docs.aws.amazon.com/ko_kr/apigateway/latest/developerguide/apigateway-websocket-api-overview.html
https://github.com/socketio/socket.io-client/issues/1370
https://lacti.github.io/2019/07/07/websocket-api/
https://docs.aws.amazon.com/apigateway/latest/developerguide/limits.html