[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