소켓통신은 소켓서버가 필수로 요구된다.
때문에 언제 접속할지 모르는 client를 위해 서버를 계속 가동하고 있어야 한다.
// 구조도
Client
│
┌────────────────────── AWS API Gateway ──────────────────────┐
│ │
│ $onConnection echo $onDisconnection │
└───────┼─────────────────────┼──────────────────────┼────────┘
│ │ │
AWS Lambda AWS Lambda AWS Lambda
│ │ │
└─────────────────────┼──────────────────────┘
┌─────┴─────┐
│ Dynamo DB │
└───────────┘
const WebSocket = require('ws');
const readline = require('readline');
const url = process.argv[2];
const ws = new WebSocket(url);
ws.on('open', () => console.log('connected'));
ws.on('message', data => console.log(`${data}`));
ws.on('close', () => {
console.log('disconnected');
process.exit();
});
const r1 = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
r1.on('line', data => {
const message = JSON.stringify({action: 'echo', data: data});
ws.send(message);
});
const WebSocket = require('ws');
const short = require('short-uuid');
const connections = {};
const send = (connectionId, data) => {
const connection = connections[connectionId];
connection.send(data);
}
const defaultActions = {
connect: (connection) => {
const id = short.generate();
connection.connectionId = id
connections[id] = connection;
console.log(`client connected with connectionId: ${id}`);
customActions.connect && customActions.connect(id);
},
disconnect: (connectionId) => {
delete connections[connectionId];
console.log(`client disconnected with connectionId: ${connectionId}`);
customActions.disconnect && customActions.disconnect(connectionId);
},
default: (connectionId, message) => {
customActions.default ? customActions.default(connectionId) :
send(connectionId, message ? `unrecognized action: ${message.action}`
: `message cannot be empty`)
},
};
const customActions = {
echo: (connectionId, data) => {
send(connectionId, data);
}
};
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', socket => {
defaultActions.connect(socket);
socket.on('message', messageJson => {
console.log(`Received: ${messageJson}`);
try {
const { action, data } = JSON.parse(messageJson);
// call the matching custom handler, else call the default handler
const customHandler = customActions[action];
customHandler ? customHandler(socket.connectionId, data) :
defaultActions.default(socket.connectionId, { action, data });
} catch (ex) {
console.error(ex);
socket.send(`Bad Request format, use: '{"action": ..., "data": ...}'`);
}
});
socket.on('close', () => {
defaultActions.disconnect(socket.connectionId);
});
});
console.log(`Listening on ws://localhost:8080`);
# Console 1
node server.js
# Console 2
node client.js ws://localhost:8080
// apply the patch
require('./patch.js');
const AWS = require('aws-sdk');
let send = undefined;
function init(event) {
const apigwManagementApi = new AWS.ApiGatewayManagementApi({
apiVersion: '2018-11-29',
endpoint: event.requestContext.domainName + '/' + event.requestContext.stage
});
send = async (connectionId, data) => {
await apigwManagementApi.postToConnection({ ConnectionId: connectionId, Data: `Echo: ${data}` }).promise();
}
}
exports.handler = async(event) => {
init(event);
const connectionId = event.requestContext.connectionId;
let data = JSON.parse(event.body).data
await send(connectionId, data);
// the return value is ignored when this function is invoked from WebSocket gateway
return {};
};
require('aws-sdk/lib/node_loader');
var AWS = require('aws-sdk/lib/core');
var Service = AWS.Service;
var apiLoader = AWS.apiLoader;
apiLoader.services['apigatewaymanagementapi'] = {};
AWS.ApiGatewayManagementApi = Service.defineService('apigatewaymanagementapi', ['2018-11-29']);
Object.defineProperty(apiLoader.services['apigatewaymanagementapi'], '2018-11-29', {
get: function get() {
var model = {
"metadata": {
"apiVersion": "2018-11-29",
"endpointPrefix": "execute-api",
"signingName": "execute-api",
"serviceFullName": "AmazonApiGatewayManagementApi",
"serviceId": "ApiGatewayManagementApi",
"protocol": "rest-json",
"jsonVersion": "1.1",
"uid": "apigatewaymanagementapi-2018-11-29",
"signatureVersion": "v4"
},
"operations": {
"PostToConnection": {
"http": {
"requestUri": "/@connections/{connectionId}",
"responseCode": 200
},
"input": {
"type": "structure",
"members": {
"Data": {
"type": "blob"
},
"ConnectionId": {
"location": "uri",
"locationName": "connectionId"
}
},
"required": [
"ConnectionId",
"Data"
],
"payload": "Data"
}
}
},
"shapes": {}
}
model.paginators = {
"pagination": {}
}
return model;
},
enumerable: true,
configurable: true
});
module.exports = AWS.ApiGatewayManagementApi;
# ex) $WEB_SOCKET_URL: wss://1234567890.execute-api.ap-northeast-2.amazonaws.com/WebSocketStage
node client.js $WEB_SOCKET_URL
# 아래와 같은 결과가 출력된다.
Lambda 함수 이름 | 핸들러 | 환경 변수 | File | 역할(role) |
---|---|---|---|---|
WSDemoEchoHandler_onConnection | app.handler | Key: TABLE_NAME Value: WSDemo | ./WSDemoEchoHandler_onConnection/* | 파일첨부 |
WSDemoEchoHandler | index.handler | Key: TABLE_NAME Value: WSDemo | ./WSDemoEchoHandler/* | 사진 첨부(Basic Execution Role + 나머지 |
WSDemoEchoHandler_onDisconnection | app.handler | Key: TABLE_NAME Value: WSDemo | ./WSDemoEchoHandler_onDisconnection/* | 파일첨부 |
소켓 통신을 람다로 구현 하셨는데 비용은 어느정도 청구 되나요? 람다가 계속적으로 켜져있다면 상당한 비용이 청구 될것 같습니다.