댓글 검색 목록

[Nodejs] Node.js 및 Socket.io로 실시간 웹앱 빌드

페이지 정보

작성자 운영자 작성일 21-02-27 22:45 조회 1,832 댓글 0

이 블로그 게시물에서 우리는 전 세계의 민주적 기관과 관행을 지원하는 NGO 인 National Democratic Institute를 위해 최근에 완료 한 프로젝트를 보여줍니다. NDI의 임무는 정치 및 시민 단체를 강화하고, 선거를 보호하며, 정부에 대한 시민 참여, 개방성 및 책임을 촉진하는 것입니다.


우리의 임무는 사이버 보안 테마 대화형 시뮬레이션 게임의 촉진자를 지원하는 애플리케이션의 MVP를 구축하는 것이었습니다. 이 웹앱은 서로 다른 컴퓨터의 여러 사람이 동시에 사용해야 하므로 Socket.io를 사용하여 구현한 실시간 동기화가 필요했습니다.


다음 기사에서는 우리가 프로젝트에 어떻게 접근했는지, 데이터 액세스 계층을 구조화 한 방법, 웹 소켓 서버 생성과 관련된 문제를 해결 한 방법에 대해 자세히 알아볼 수 있습니다. 프로젝트의 최종 코드는 오픈 소스이며 Github에서 자유롭게 확인할 수 있습니다.


CyberSim 프로젝트에 대한 간략한 개요 


정당은 해커와 다른 적들에게 극심한 위험에 처해 있지만 그들이 직면한 위협의 범위를 거의 이해하지 못합니다. 사이버 보안 교육을 받으면 종종 지루하고 기술적으로 복잡한 강의의 형태로 진행됩니다. 파티와 캠페인이 당면한 문제를 더 잘 이해할 수 있도록 NDI는 다양한 보안 사고로 인해 흔들리는 정치 캠페인에 대한 사이버 보안 시뮬레이션 (CyberSim)을 개발했습니다. CyberSim의 목표는 정치 캠페인이 자신의 준비 상태를 평가하고 완화되지 않은 위험의 잠재적인 결과를 경험하도록 지원함으로써 더 나은 보안 관행에 대한 동의 및 구현을 촉진하는 것입니다.


CyberSim은 준비, 시뮬레이션 및 사후 검토의 세 가지 핵심 세그먼트로 나뉩니다. 준비 단계에서 참가자는 가상 (그러나 현실적인) 게임 플레이 환경, 역할 및 게임 규칙을 소개 받습니다. 또한 제한된 예산에서 보안 관련 완화를 선택할 수 있는 기회가 주어지며 시뮬레이션이 시작되기 전에 지식과 능력을 최대한 활용하여 "시스템을 보호"할 수 있는 기회를 제공합니다.


real-time-nodejs-app-with-websocket-socket-io 

시뮬레이션 자체는 75 분 동안 실행되며,이 시간 동안 참가자는 자금을 모으고 후보에 대한 지원을 강화하며 가장 중요한 것은 캠페인의 성공에 부정적인 영향을 미칠 수 있는 이벤트에 대응할 수 있는 조치를 취할 수 있습니다. 이러한 이벤트는 정보 보안 모범 사례와 관련된 참가자의 준비 상태, 인식 및 기술을 테스트하기 위한 것입니다. 시뮬레이션은 일반적인 캠페인 환경의 분주함과 강도를 반영하도록 설계되었습니다.


socketio-actions 


사후 검토는 여러면에서 CyberSim 실행의 가장 중요한 요소입니다. 이 세그먼트에서 CyberSim 진행자와 참가자는 시뮬레이션 중에 발생한 일, 시뮬레이션 중에 어떤 이벤트로 인해 문제가 발생하는지, 보안 사고 발생을 방지하기 위해 참가자가 취한 (또는 취해야 했던) 조치를 검토합니다. 이러한 강의는 사이버 보안 캠페인 플레이 북에 제시된 모범 사례와 밀접하게 일치하므로 CyberSim은 기존 지식을 강화하거나 거기에 제시된 새로운 모범 사례를 도입 할 수 있는 이상적인 기회가 됩니다.


assessment-screen-socketio 


데이터 표현이 각 애플리케이션의 골격 역할을 하기 때문에 앱의 일부를 구축 한 Norbert는 먼저 knex 및 Node.js를 사용하여 생성 된 데이터 계층을 안내합니다. 그런 다음 그는 실시간 통신을 관리하는 소켓 서버 인 프로그램의 난로로 이동합니다.


이것은 일련의 기사가 될 것이므로 다음 부분에서는 React로 빌드 된 프론트 엔드를 살펴볼 것입니다. 마지막으로 세 번째 게시물에서 Norbert는 프로젝트의 인프라 인 근육을 소개 할 것입니다. Amazon의 도구를 사용하여 CI / CD를 만들고 웹 서버, 정적 프런트 엔드 앱 및 데이터베이스를 호스팅했습니다.


이제 소개를 마쳤으므로 Norbert의 Socket.io 자습서 / 사례 연구를 읽을 수 있습니다.


프로젝트 구조 


데이터 액세스 계층에 대해 자세히 알아보기 전에 프로젝트의 구조를 살펴 보겠습니다.


. ├── migrations │ └── ... ├── seeds │ └── ... ├── src │ ├── config.js │ ├── logger.js │ ├── constants │ │ └── ... │ ├── models │ │ └── ... │ ├── util │ │ └── ... │ ├── app.js │ └── socketio.js └── index.js 



보시다시피 구조는 표준 Node.js 프로젝트 구조에서 실제로 벗어나지 않기 때문에 비교적 간단합니다. 애플리케이션을 더 잘 이해하기 위해 데이터 모델부터 시작하겠습니다.


데이터 액세스 계층 


각 게임은 사전 프로그래밍 된 투표 비율과 사용 가능한 예산으로 시작됩니다. 게임 내내 위협 (주입이라고 함)은 플레이어가 대응해야 하는 미리 정의 된 시간 (예 : 2 분)에 발생합니다. 일을 꾸미기 위해 직원은 대응하고 조치를 취하는 데 필요한 여러 시스템을 갖추고 있습니다. 이러한 시스템은 종종 주사의 결과로 다운됩니다. 게임의 최종 목표는 간단합니다. 플레이어는 각 위협에 응답하여 파티의 투표를 극대화 해야 합니다.


PostgreSQL 데이터베이스를 사용하여 각 게임의 상태를 저장했습니다. 데이터 모델을 구성하는 테이블은 설정 및 상태 테이블의 두 가지 그룹으로 분류 할 수 있습니다. 설정 테이블은 다음과 같이 각 게임에 대해 동일하고 일정한 데이터를 저장합니다.


  • injections-게임 중에 직면하는 각 위협 플레이어가 포함됩니다 (예 : Databreach).
  • injection responses-각 주입에 대해 가능한 반응을 보여주는 일대 다 표
  • action-캠페인 광고와 같이 즉각적인 정시 효과가 있는 작업
  • systems-특정 대응 및 조치의 전제 조건 인 유형 및 무형 IT 자산 (예 : HQ Computers)
  • mitigations-다가오는 주입을 완화하는 유형 및 무형 자산 (예 : 온라인 정당 유권자 데이터베이스에 대한 안전한 백업 생성)
  • roles-캠페인 파티의 여러 부서 (예 : HQ IT 팀)
  • curveball events-진행자가 제어하는 ​​일회성 이벤트 (예 : 뱅킹 시스템 충돌)

반면에 상태 테이블은 게임의 상태를 정의하고 시뮬레이션 중에 변경됩니다. 이 테이블은 다음과 같습니다.


  • game-예산, 여론 조사 등과 같은 게임의 속성
  • game systems-게임 전반에 걸쳐 각 시스템의 상태 (온라인 또는 오프라인)를 저장합니다.
  • game mitigations-플레이어가 각 완화를 구매했는지 보여줍니다.
  • game injection-발생한 주입에 대한 정보 (예 : 예방 여부, 이에 대한 응답)를 저장합니다.
  • game log


데이터베이스 스키마를 시각화 하는 데 도움이 되도록 다음 다이어그램을 살펴보십시오. game_log 테이블은 그림에 불필요한 복잡성을 추가하고 실제로 게임의 핵심 기능을 이해하는 데 도움이 되지 않기 때문에 이미지에서 의도적으로 남겨진 것입니다.


database_schema_socketio 


요약하면 상태 테이블은 항상 진행 중인 게임의 현재 상태를 저장합니다. 진행자가 수행 한 각 수정 사항은 저장 한 다음 모든 코디네이터에게 다시 전송해야 합니다. 이를 위해 상태가 업데이트 된 후 다음 함수를 호출하여 게임의 현재 상태를 반환하는 메서드를 데이터 액세스 계층에 정의했습니다.


socketio_db_schema_faded 


// ./src/game.js
const db = require('./db');

const getGame = (id) =>
db('game')
  .select(
    'game.id',
    'game.state',
    'game.poll',
    'game.budget',
    'game.started_at',
    'game.paused',
    'game.millis_taken_before_started',
    'i.injections',
    'm.mitigations',
    's.systems',
    'l.logs',
  )
  .where({ 'game.id': id })
  .joinRaw(
    `LEFT JOIN (SELECT gm.game_id, array_agg(to_json(gm)) AS mitigations FROM game_mitigation gm GROUP BY gm.game_id) m ON m.game_id = game.id`,
  )
  .joinRaw(
    `LEFT JOIN (SELECT gs.game_id, array_agg(to_json(gs)) AS systems FROM game_system gs GROUP BY gs.game_id) s ON s.game_id = game.id`,
  )
  .joinRaw(
    `LEFT JOIN (SELECT gi.game_id, array_agg(to_json(gi)) AS injections FROM game_injection gi GROUP BY gi.game_id) i ON i.game_id = game.id`,
  )
  .joinRaw(
    `LEFT JOIN (SELECT gl.game_id, array_agg(to_json(gl)) AS logs FROM game_log gl GROUP BY gl.game_id) l ON l.game_id = game.id`,
  )
  .first();


const db = require ( './ db'); line은 데이터베이스 쿼리 및 업데이트에 사용되는 knex를 통해 설정된 데이터베이스 연결을 반환합니다. 위의 함수를 호출하면 이미 구매하고 아직 판매 가능한 각 완화 조치, 온라인 및 오프라인 시스템, 발생한 주입, 게임 로그를 포함하여 게임의 현재 상태를 검색 할 수 있습니다. 다음은 퍼실리테이터가 커브 볼 이벤트를 트리거 한 후이 논리가 적용되는 방법의 예입니다.


// ./src/game.js
const performCurveball = async ({ gameId, curveballId }) => {
 try {
   const game = await db('game')
     .select(
       'budget',
       'poll',
       'started_at as startedAt',
       'paused',
       'millis_taken_before_started as millisTakenBeforeStarted',
     )
     .where({ id: gameId })
     .first();

   const { budgetChange, pollChange, loseAllBudget } = await db('curveball')
     .select(
       'lose_all_budget as loseAllBudget',
       'budget_change as budgetChange',
       'poll_change as pollChange',
     )
     .where({ id: curveballId })
     .first();

   await db('game')
     .where({ id: gameId })
     .update({
       budget: loseAllBudget ? 0 : Math.max(0, game.budget + budgetChange),
       poll: Math.min(Math.max(game.poll + pollChange, 0), 100),
     });

   await db('game_log').insert({
     game_id: gameId,
     game_timer: getTimeTaken(game),
     type: 'Curveball Event',
     curveball_id: curveballId,
   });
 } catch (error) {
   logger.error('performCurveball ERROR: %s', error);
   throw new Error('Server error on performing action');
 }
 return getGame(gameId);
};


살펴볼 수 있듯이, 이번에는 예산 및 설문 조사의 변경 인 게임 상태에 대한 업데이트가 발생한 후 프로그램이 getGame 함수를 호출하고 그 결과를 반환합니다. 이 로직을 적용하면 상태를 쉽게 관리 할 수 ​​있습니다. 우리는 같은 게임의 각 코디네이터를 그룹으로 배열하고, 어떻게 든 각 가능한 이벤트를 모델 폴더의 해당 기능에 매핑하고, 누군가 변경 한 후 모든 사람에게 게임을 브로드 캐스트해야 합니다. WebSocket을 활용하여 어떻게 달성했는지 살펴 보겠습니다.


Node.js로 Real-Time Socket.io 서버 만들기 


우리가 만든 소프트웨어는 다른 위치에서 플레이 되는 실제 테이블 탑 게임의 동반자 앱이므로 실시간으로 진행됩니다. UI의 상태를 여러 클라이언트에서 동기화해야 하는 이러한 사용 사례를 처리하기 위해 WebSocket이 이동 솔루션입니다. WebSocket 서버 및 클라이언트를 구현하기 위해 Socket.io를 사용하기로 선택했습니다. Socket.io에는 분명히 엄청난 성능 오버 헤드가 있지만 WebSocket 연결의 안정적인 특성으로 인해 발생하는 많은 번거로움에서 벗어날 수 있었습니다. 예상되는 로드가 미미했기 때문에 Socket.io가 도입 한 오버 헤드는 개발 시간 절약으로 인해 가려졌습니다. 우리의 사용 사례에 매우 잘 맞는 Socket.io의 킬러 기능 중 하나는 동일한 게임에 참여하는 운영자가 socket.io 룸을 사용하여 쉽게 분리 될 수 있다는 것입니다. 이렇게 하면 참가자가 게임을 업데이트 한 후 전체 룸 (현재 특정 게임에 참가한 모든 사람)에 새 상태를 브로드 캐스트 할 수 있습니다.


소켓 서버를 생성하려면 기본 Node.js http 모듈의 createServer 메소드로 생성 된 Server 인스턴스만 있으면 됩니다. 유지 관리를 위해 socket.io 로직을 별도의 모듈로 구성했습니다 (참조 : .src / socketio.js). 이 모듈은 하나의 인수 인 http Server 객체와 함께 팩토리 함수를 내 보냅니다. 한 번 살펴 보겠습니다.


// ./src/socketio.js
const socketio = require('socket.io');

const SocketEvents = require('./constants/SocketEvents');

module.exports = (http) => {
const io = socketio(http);

io.on(SocketEvents.CONNECT, (socket) => {
  socket.on('EVENT', (input) => {
      // DO something with the given input
  })
}
}



// index.js const { createServer } = require('http'); const app = require('./src/app'); // Express app const createSocket = require('./src/socketio'); const port = process.env.PORT || 3001; const http = createServer(app); createSocket(http); const server = http.listen(port, () => { logger.info(`Server is running at port: ${port}`); });



보시다시피 소켓 서버 로직은 팩토리 기능 내부에서 구현됩니다. index.js 파일에서이 함수는 http 서버와 함께 호출됩니다. 이 프로젝트 중에 인증을 구현할 필요가 없었으므로 연결을 설정하기 전에 각 클라이언트를 인증하는 socket.io 미들웨어가 없습니다. socket.io 모듈 내에서 주입에 대한 응답 문서화, 완화 구매, 시스템 복원 등을 포함하여 촉진자가 수행 할 수있는 각 가능한 작업에 대한 이벤트 처리기를 만들었습니다. 그런 다음 데이터 액세스 계층에 정의 된 메서드를 다음에 매핑했습니다. 이 핸들러.


진행자 모으기 


이전에 룸을 사용하면 현재 참여한 게임을 통해 진행자를 쉽게 구분할 수 있다고 언급했습니다. 진행자는 새로운 게임을 만들거나 기존 게임에 참여하여 룸에 들어갈 수 있습니다. 이것을 "WebSocket 언어"로 번역하면 클라이언트는 createGame 또는 joinGame 이벤트를 내 보냅니다. 해당 구현을 살펴 보겠습니다.


// ./src/socketio.js
const socketio = require('socket.io');

const SocketEvents = require('./constants/SocketEvents');
const logger = require('./logger');
const {
 createGame,
 getGame,
} = require('./models/game');

module.exports = (http) => {
 const io = socketio(http);

 io.on(SocketEvents.CONNECT, (socket) => {
   logger.info('Facilitator CONNECT');
   let gameId = null;

   socket.on(SocketEvents.DISCONNECT, () => {
     logger.info('Facilitator DISCONNECT');
   });

   socket.on(SocketEvents.CREATEGAME, async (id, callback) => {
     logger.info('CREATEGAME: %s', id);
     try {
       const game = await createGame(id);
       if (gameId) {
         await socket.leave(gameId);
       }
       await socket.join(id);
       gameId = id;
       callback({ game });
     } catch (_) {
       callback({ error: 'Game id already exists!' });
     }
   });

   socket.on(SocketEvents.JOINGAME, async (id, callback) => {
     logger.info('JOINGAME: %s', id);
     try {
       const game = await getGame(id);
       if (!game) {
         callback({ error: 'Game not found!' });
       }
       if (gameId) {
         await socket.leave(gameId);
       }
       await socket.join(id);
       gameId = id;
       callback({ game });
     } catch (error) {
       logger.error('JOINGAME ERROR: %s', error);
       callback({ error: 'Server error on join game!' });
     }
   });
 }
}


위의 코드 스니펫을 살펴보면 gameId 변수에는 현재 참여한 진행자 인 게임의 ID가 포함됩니다. 자바 스크립트 클로저를 활용하여 이 변수를 connect 콜백 함수 내에 선언했습니다. 따라서 gameId 변수는 다음 모든 핸들러의 범위에 있습니다. 주최자가 이미 플레이 하는 동안 게임을 만들려고 하면 (즉, gameId가 null이 아님) 소켓 서버는 먼저 이전 게임의 방에서 진행자를 쫓아 낸 다음 새 게임방의 진행자와 합류합니다. 이것은 탈퇴 및 가입 방법으로 관리됩니다. joinGame 핸들러의 프로세스 흐름은 거의 동일합니다. 유일한 주요 차이점은 이번에는 서버가 새 게임을 만들지 않는다는 것입니다. 대신 데이터 액세스 레이어의 악명 높은 getGame 메서드를 사용하여 이미 존재하는 것을 쿼리합니다.


이벤트 핸들러는 무엇입니까? 


진행자를 성공적으로 모은 후 가능한 각 이벤트에 대해 다른 핸들러를 만들어야 했습니다. 완전성을 위해 게임 중에 발생하는 모든 이벤트를 살펴 보겠습니다.


  • createGame, joinGame :이 이벤트의 유일한 목적은 올바른 게임 룸 주최자에 참여하는 것입니다.
  • startSimulation, pauseSimulation, finishSimulation :이 이벤트는 이벤트의 타이머를 시작하고 타이머를 일시 중지하고 게임을 완전히 중지하는 데 사용됩니다. 누군가 finishGame 이벤트를 생성하면 다시 시작할 수 없습니다.
  • deliveryInjection :이 이벤트를 사용하여 촉진자는 게임의 주어진 시간에 발생해야 하는 보안 위협을 트리거 합니다.
  • respondToInjection, nonCorrectRespondToInjection : 이러한 이벤트는 주입에 대한 응답을 기록합니다.
  • restoreSystem :이 이벤트는 인젝션으로 인해 오프라인 상태 인 모든 시스템을 복원하는 것입니다.
  • changeMitigation :이 이벤트는 플레이어가 주입을 방지하기 위해 완화를 구매할 때 트리거됩니다.
  • performAction : 연주하는 스태프가 액션을 수행 할 때 클라이언트는 이 이벤트를 서버에 내 보냅니다.
  • performCurveball :이 이벤트는 진행자가 고유 한 주입을 트리거 할 때 발생합니다.


이러한 이벤트 핸들러는 다음 규칙을 구현합니다.

  • 최대 2 개의 인수, 각 이벤트마다 다른 선택적 입력과 사전 정의 된 콜백을 사용합니다. 콜백은 승인이라고 하는 socket.io의 흥미로운 기능입니다. 이를 통해 클라이언트 측에 콜백 함수를 생성 할 수 있으며, 서버가 오류 또는 게임 객체로 호출 할 수 있습니다. 이 호출은 클라이언트 측에 영향을 미칩니다. 프런트 엔드가 작동하는 방식을 자세히 살펴 보지 않고 (다음 날의 주제이므로)이 함수는 오류 또는 성공 메시지와 함께 경고를 표시합니다. 이 메시지는 이벤트를 시작한 진행자에게만 나타납니다.
  • 이벤트의 성격에 따라 주어진 입력으로 게임의 상태를 업데이트합니다.
  • 그들은 게임의 새로운 상태를 방 전체에 방송합니다. 따라서 그에 따라 모든 주최자의 보기를 업데이트 할 수 있습니다.

먼저 이전 예제를 기반으로 처리기가 curveball 이벤트를 구현하는 방법을 살펴 보겠습니다.


// ./src/socketio.js const socketio = require('socket.io'); const SocketEvents = require('./constants/SocketEvents'); const logger = require('./logger'); const { performCurveball, } = require('./models/game'); module.exports = (http) => { const io = socketio(http); io.on(SocketEvents.CONNECT, (socket) => { logger.info('Facilitator CONNECT'); let gameId = null; socket.on( SocketEvents.PERFORMCURVEBALL, async ({ curveballId }, callback) => { logger.info( 'PERFORMCURVEBALL: %s', JSON.stringify({ gameId, curveballId }), ); try { const game = await performCurveball({ gameId, curveballId, }); io.in(gameId).emit(SocketEvents.GAMEUPDATED, game); callback({ game }); } catch (error) { callback({ error: error.message }); } }, ); } }



curveball 이벤트 핸들러는 앞서 언급 한 바와 같이 하나의 입력, curveballId 및 콜백을 받습니다. 그런 다음 performCurveball 메서드는 게임의 투표 및 예산을 업데이트하고 새 게임 개체를 반환합니다. 업데이트가 성공하면 소켓 서버는 최신 상태로 게임 룸에 gameUpdated 이벤트를 내 보냅니다. 그런 다음 게임 개체와 함께 콜백 함수를 호출합니다. 오류가 발생하면 오류 개체와 함께 호출됩니다.


진행자가 게임을 만든 후 먼저 플레이어를 위한 준비보기가로드됩니다. 이 단계에서 직원은 게임이 시작되기 전에 완화를 구매하기 위해 예산의 일부를 사용할 수 있습니다. 게임이 시작되면 일시 중지하거나 다시 시작하거나 영구적으로 중지 할 수도 있습니다. 해당 구현을 살펴 보겠습니다.


// ./src/socketio.js const socketio = require('socket.io'); const SocketEvents = require('./constants/SocketEvents'); const logger = require('./logger'); const { startSimulation, pauseSimulation } = require('./models/game'); module.exports = (http) => { const io = socketio(http); io.on(SocketEvents.CONNECT, (socket) => { logger.info('Facilitator CONNECT'); let gameId = null; socket.on(SocketEvents.STARTSIMULATION, async (callback) => { logger.info('STARTSIMULATION: %s', gameId); try { const game = await startSimulation(gameId); io.in(gameId).emit(SocketEvents.GAMEUPDATED, game); callback({ game }); } catch (error) { callback({ error: error.message }); } }); socket.on(SocketEvents.PAUSESIMULATION, async (callback) => { logger.info('PAUSESIMULATION: %s', gameId); try { const game = await pauseSimulation({ gameId }); io.in(gameId).emit(SocketEvents.GAMEUPDATED, game); callback({ game }); } catch (error) { callback({ error: error.message }); } }); socket.on(SocketEvents.FINISHSIMULATION, async (callback) => { logger.info('FINISHSIMULATION: %s', gameId); try { const game = await pauseSimulation({ gameId, finishSimulation: true }); io.in(gameId).emit(SocketEvents.GAMEUPDATED, game); callback({ game }); } catch (error) { callback({ error: error.message }); } }); } }



startSimulation은 게임의 타이머를 시작하고 pauseSimulation 메서드는 게임을 일시 중지 및 중지합니다. 트리거 시간은 어떤 주입 촉진자가 호출 할 수 있는지 결정하는 데 필수적입니다. 주최자는 위협을 유발 한 후 필요한 모든 자산을 플레이어에게 넘깁니다. 그런 다음 직원은 사용자 지정 응답을 제공하거나 미리 정의 된 옵션에서 선택하여 주입에 대한 응답 방식을 선택할 수 있습니다. 위협에 직면 한 직원들은 조치를 취하고 시스템을 복원하고 완화 조치를 구매합니다. 이러한 활동에 해당하는 이벤트는 게임 중 언제든지 트리거 될 수 있습니다. 이러한 이벤트 핸들러는 동일한 패턴을 따르고 세 가지 기본 규칙을 구현합니다. 이러한 콜백을 검사하려면 공용 GitHub 저장소를 확인하십시오.


설정 데이터 제공 


데이터 액세스 계층을 설명하는 장에서 테이블을 설정 및 상태 테이블의 두 가지 그룹으로 분류했습니다. 상태 테이블에는 진행 중인 게임의 상태가 포함됩니다. 이 데이터는 이벤트 기반 소켓 서버를 통해 제공되고 업데이트 됩니다. 반면에 설정 데이터는 사용 가능한 시스템, 게임 완화, 작업 및 커브 볼 이벤트, 게임 중에 발생하는 주입 및 이에 대한 가능한 각 응답으로 구성됩니다. 이 데이터는 간단한 http 서버를 통해 노출됩니다. 퍼실리테이터가 게임에 참여하면 React 클라이언트는 이 데이터를 요청하고 게임 전체에서 캐시하고 사용합니다. HTTP 서버는 Express 라이브러리를 사용하여 구현됩니다. app.js를 살펴 보겠습니다.


// .src/app.js
const helmet = require('helmet');
const express = require('express');
const cors = require('cors');
const expressPino = require('express-pino-logger');

const logger = require('./logger');
const { getResponses } = require('./models/response');
const { getInjections } = require('./models/injection');
const { getActions } = require('./models/action');

const app = express();

app.use(helmet());
app.use(cors());
app.use(
 expressPino({
   logger,
 }),
);

// STATIC DB data is exposed via REST api

app.get('/mitigations', async (req, res) => {
 const records = await db('mitigation');
 res.json(records);
});

app.get('/systems', async (req, res) => {
 const records = await db('system');
 res.json(records);
});

app.get('/injections', async (req, res) => {
 const records = await getInjections();
 res.json(records);
});

app.get('/responses', async (req, res) => {
 const records = await getResponses();
 res.json(records);
});

app.get('/actions', async (req, res) => {
 const records = await getActions();
 res.json(records);
});

app.get('/curveballs', async (req, res) => {
 const records = await db('curveball');
 res.json(records);
});

module.exports = app;


보시다시피 모든 것이 여기에서 꽤 표준입니다. 이 데이터는 시드를 사용하여 삽입 및 변경되므로 GET 이외의 방법을 구현할 필요가 없습니다.


Socket.io 게임에 대한 최종 생각 


이제 우리는 백엔드가 어떻게 작동하는지 모을 수 있습니다. 상태 테이블은 게임의 상태를 저장하고 데이터 액세스 계층은 각 업데이트 후 새 게임 상태를 반환합니다. 소켓 서버는 진행자를 방으로 구성하므로 누군가가 무언가를 변경할 때마다 새 게임이 방 전체에 방송됩니다. 따라서 우리는 모든 사람이 게임에 대한 최신 시각을 갖도록 할 수 있습니다. 동적 게임 데이터 외에도 http 서버를 통해 정적 테이블에 액세스 할 수 있습니다.


다음에는 React 클라이언트가 이 모든 것을 관리하는 방법을 살펴보고 그 후에 프로젝트 뒤에 있는 인프라를 소개하겠습니다. 공개 GitHub 저장소에서 이 앱의 코드를 확인할 수 있습니다!


https://blog.risingstack.com/real-time-node-js-webapp-socket-io/



댓글목록 0

등록된 댓글이 없습니다.

웹학교 로고

온라인 코딩학교

코리아뉴스 2001 - , All right reserved.