Sprint - Chatterbox Server

조 은길·2022년 2월 6일
0

Sprint

목록 보기
18/19
post-thumbnail

Chatterbox Server

이번 과제는 클라이언트에서 서버까지의 구조를 알아보는 여정의 일부입니다. 지난번에 만든 Chatterbox client-side app은 이미 만들어진 AWS 서버와 연결했다면, 오늘부터 진행할 스프린트에서는 이전의 서버를 떼어내고 여러분이 Node.js를 사용해 만들 로컬과 연결시킬 것입니다.

새로 만들 서버는 유저가 웹 브라우저에서 접속하고, username를 고르고, 메시지를 보내고, 같은 서버에 접속해있는 모든 유저의 메시지를 읽을 수 있게 해주어야 합니다.

이번 스프린트 역시 여러분이 주도적으로 문제를 해결할 수 있도록 설계되었습니다. 문제를 "어떻게" 해결할지에 관해 생각해보기도 전에 "무엇이" 문제인지를 파악하는 것부터가 시급한 과제가 되는 상황이 많이 발생합니다. 지시사항이 명확하지 않고 해결하기 어려운 문제를 마주해야 하는 상황에서, 생산적으로 고민하고 문제를 차근차근 해결해나갈 수 있도록 하는 도전 의식을 잃지 않기를 바랍니다. 익숙하지 않은 데다가 제대로 작성된 공식문서조차 없더라도 작업의 방향성을 찾고 과제를 완수할 수 있는 능력은 software engineer로서 성공하는 데에 크게 기여해줄 것입니다.

Project Setting

1. what's in this repo

  • server/server/basic-server.js 는 아주 잘 문서화 된 HTTP 뼈대 서버 입니다. 코드의 주석은 가이드와 어떻게 실행하는지까지 포함되어 있습니다.
  • server/server/request-hanlder.js 는 여러분들이 작업해야 할 코드이며 현재로서는 거의 비어있을 겁니다.
  • server/test/ 은 Jest 스펙을 가지고 있으며 여러분들의 서버를 확장 해 나감에 따라서 더많은 테스트 스펙들을 작성 하여야 합니다.

2. Getting Started

  • npm install 을 통해서 package.json에 설정 되어있는 패키지 들을 설치 합니다
  • chatterbox-client때 작성하였던 코드를 이번 스프린트 코드에 합칩니다.(테스트 케이스 제외)
  • 단, 본인의 코드가 고치는데 시간이 걸린다면 레퍼런스 코드를 활용 하세요. 이번 스프린트의 목적은 서버를 구축하는 것에 있습니다. 클라이언트 코드를 고치는데 시간을 할애하지 마세요.
  • 이번 스프린트에서는 요청하는 URL에 깃허브 아이디가 포함되지 않아도 괜찮습니다. /:GithubID/messgae대신 /messages 에 대해 GET, POST 요청 및 해당 요청에 대한 응답을 전송하도록 하세요.

Bare Minimum Requirements

  • Nodemon을 활용해서, 서버 application이 자동으로 재시작 될 수 있도록 셋업 하세요.
  • http서버를 시작하기 위해서 NodeJS를 어떻게 사용하는지 학습하고, 어떻게 브라우저에서 서버를 연결 하는지 확인 하세요.(http 서버의 베이스 코드는 server/basic-server.js에 작성 되어 있습니다.)
  • 프론트엔드 개발자를 위한 서버 API 문서를 제작 하세요.
  • 실질적으로 request를 처리하는 로직은 server/request-handler.js에 작성 되어 있습니다. handler를 basic-server에서 활용 할 수 있도록 export와 require를 활용해서 코드를 작성 하세요. (CommonJS 강의를 참고하세요!)
  • chatterbox-client 에서 이미 작성되어있는 코드 중 server URL을 원격 주소(http://52.78.213.9:3000/)가 아닌 나의 로컬 주소 http://localhost:3000/ 로 수정하세요.
  • 모든 JEST 테스트를 통과 하세요
    • yarn or npm install 을 통해서 의존 모듈을 설치 하세요
    • root 디렉토리에서 npm test를 통해서 테스트를 실행하세요.

Advanced Challenges

Advanced 콘텐츠는 여러분이 초보 개발자를 넘어 현업 엔지니어에게 기대할 법한 요구사항들로 이뤄져 있습니다. 문제를 해결하려는 시도에 앞서 충분한 컨텍스트가 필요할 수 있습니다. (예를 들어 socket.io를 이용한 요구사항을 충족시키기 위해 Websocket 프로토콜에 대한 지식을 요구할 수 있습니다) 그럼에도 불구하고, 도전해볼만 한 가치가 있으며, 여러분의 현재 수준을 넘어서는 기술적 성취를 도울 것입니다.

  • fs 모듈을 이용하여, 서버가 메시지들의 목록을 파일로 저장할 수 있도록 만드세요. 파일로 메시지들이 저장되면, 서버가 재시작하더라도 다시 메시지 목록을 불러올 수 있게 됩니다.
  • 여러분의 채팅 앱을 socket.io를 이용해 완전히 새롭게 작성해보세요. 이는 HTTP가 아닌, 완전한 실시간 통신을 지원합니다. 따라서 더이상 새로운 메시지를 fetch하기 위해 setInterval을 사용할 필요가 없어집니다.
  • 여러분의 서버가 정적 파일, 즉 HTML 및 JavaScript 파일을 제공(serve)할 수 있도록 만들어보세요. 클라이언트 파일을 따로 브라우저에 여는 대신, 단지 URL http://127.0.0.1에 접속할 때, 클라이언트 페이지가 뜨도록 만드는 것이 목표입니다. 이 목표를 달성하기 위해서 fs 모듈이 필요할 것입니다. (fs.readFile)
  • 여러분의 공부 여정을 블로그에 기록하세요.

Sprint - Refactor Express

Refactor Chatterbox Server using Express

Express.js 소개

JavaScript 생태계에서, 인기있는 프레임워크의 앞글자를 따서 MERN stack으로 흔히 부르곤 합니다. (MongoDB, Express, React, Node)
이들 중 Express.js는 Node.js 환경에서 웹 어플리케이션 혹은 API를 제작하기 위해 사용되는 인기있는 프레임워크입니다.

이번 과제는 여러분이 기본 내장 node.js 모듈(http 모듈)로 작성했던 http 서버를 express 서버 프레임워크를 통해서 리팩토링 하는 것입니다.
express framework 는 npm을 통해 다운로드 받을 수 있습니다. express가 기존 http 모듈로 작성했던 서버와 갖는 큰 차이점은, 다음과 같습니다.

  1. 미들웨어를 붙이기 쉽다.
  2. 자체 라우터를 제공한다.

위 키워드를 이해할 수 있는 자료를 아래 덧붙입니다. 먼저 간단하게 웹 서버를 만들어보고, chatterbox server에 적용해봅시다.

Bare minimum requirements

Getting Started

express는 공식 문서를 따라하는 것으로도 간단한 웹 서버를 만들 수 있습니다. 위 Bare minimum requirements의 첫번째 요구사항을 달성하기 위해서는 다음과 같이 실습해보세요.

1. express 설치

공식문서의 시작하기 -> 설치 를 참고하세요.

2. 간단한 웹 서버 만들기

공식문서의 시작하기 -> Hello world 예제 를 참고하세요.

3. 라우팅: 메소드 및 URL에 따른 분기하기

앞서 Mini node server 및 Chatterbox Server 스프린트를 통해 메소드(GET 또는 POST), 그리고 URL(/lower, /upper, /messages 등)로 분기점을 만드는 것을 경험해 보았을겁니다. 이것이 다름아닌 라우팅(routing)입니다.

라우팅은 URI(또는 경로) 및 특정한 HTTP 요청 메소드(GET, POST 등)인 특정 엔드포인트에 대한 클라이언트 요청에 애플리케이션이 응답하는 방법을 결정하는 것을 말합니다.

어떠한 라이브러리를 사용하지 않고, 순수 node.js 코드를 작성하면, 다음과 같이 작성해야 합니다.

const requestHandler = (req, res) => {
  if(req.url === '/messages') {
    if (req.method === 'GET') {
      res.end(messages)
    } else if (req.method === 'POST') {
      req.on('data', (req, res) => {
        // do something ...
      })
    }
  }
}

반면에 express에는 자체 라우터 기능을 제공하므로, 라우터를 활용하면 아래와 같이 코드를 매우 직관적으로 처리할 수 있습니다.

const router = express.Router()

router.get('/messages', (req, res) =>{
  res.send(messages)
})

router.post('/messages', (req, res) =>{
  // do something
})

여기까지 익히고 나면, chatterbox server를 리팩토링 하는것이 크게 어렵게 느껴지지 않을 것입니다.


Middleware

미들웨어는 간단하게 생각하시면 컨베이어 벨트 위에 올라가있는 request에 무언가 악세사리를 덕지 덕지 붙이거나, 혹은 불량품이라면 밖으로 걷어내는 역할을 한다고 보면 좋습니다. 미들웨어는 express의 가장 큰 장점 중 하나입니다.

자주 쓰는 미들웨어

미들웨어가 주로 쓰이는 상황을 먼저 알아봅시다. 다음은 미들웨어가 주로 쓰이는 상황입니다.

  1. 모든 요청에 대해 url이나 메소드를 알고자 할 때
  2. POST 요청 등에서 쓰이는 body(payload)를 쉽게 얻어내고자 할 때
  3. 모든 요청/응답에 CORS 헤더를 붙일 때
  4. 요청 헤더에 사용자 인증 정보가 담겨있는지 확인하고 싶을 때

위와 같이, 우리가 node.js만을 이용해서 구현할 때에 다소 번거로운 작업을 미들웨어를 통해 적용하면, 보다 손쉽게 해결할 수 있습니다.

2,3번은 Express에서 흔히 사용되는 미들웨어입니다. 1,4번을 제외하고 각각의 경우를 한번 살펴봅시다. (1번, 4번은 직접 만들어볼 것입니다.)

case 2: POST 요청 등에서 쓰이는 body(payload)를 쉽게 얻어내고자 할 때

앞서 순수 node.js 코드로 HTTP body(payload)를 받을 때에는 Buffer를 조합해서 다소 복잡한 방식으로 body를 얻어내야만 합니다.

let body = [];
request.on('data', (chunk) => {
  body.push(chunk);
}).on('end', () => {
  body = Buffer.concat(body).toString();
  // body 변수에는 문자열 형태로 payload가 담겨져 있습니다.
});

이를 쉽게 만들어주는 역할을 하는 것이 바로 body-parser 미들웨어입니다.

const bodyParser = require('body-parser')
const jsonParser = bodyParser.json()

// 생략
app.post('/api/users', jsonParser, function (req, res) {
  // req.body에는 JSON의 형태로 payload가 담겨져 있습니다.
})

case 4: 모든 요청/응답에 CORS 헤더를 붙일 때

앞서 순수 node.js 코드로 CORS 헤더를 붙이려면, 응답 객체의 writeHead 메소드 등을 이용해 일일이 Access-Control-Allow-* 헤더를 정의해줘야만 했습니다. OPTIONS 메소드에 대한 라우팅도 구현해줘야만 하죠.

const defaultCorsHeader = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
  'Access-Control-Allow-Headers': 'Content-Type, Accept',
  'Access-Control-Max-Age': 10
};

// 생략
if (req.method === 'OPTIONS') {
  res.writeHead(201, defaultCorsHeader);
  res.end()
}

이를 쉽게 만들어주는 역할을 하는 것이 바로 cors 미들웨어입니다.

const cors = require('cors')

// 생략
app.use(cors()) // 모든 요청에 대해 CORS 허용
const cors = require('cors')

// 생략
// 특정 요청에 대해 CORS 허용
app.get('/products/:id', cors(), function (req, res, next) {
  res.json({msg: 'This is CORS-enabled for a Single Route'})
})

미들웨어 작동 원리 이해하기

이번엔, 직접 미들웨어를 만들어보면서 작동원리를 이해합시다. 간단하게 아래에서 작동원리를 설명하지만, 분명 공식문서를 통해 더 깊은 이해를 하게 될 것입니다.

case 1: 모든 요청에 대해 url이나 메소드를 알고자 할 때

가장 단순한 미들웨어는 logger 입니다. 말 그대로 로거는 디버깅이나, 서버 관리에 도움이 되기 위해 console.log를 적절하게 찍어주는 역할을 합니다. 미들웨어는 다음 구성을 가집니다. 공식 문서에 나온 그림을 살펴봅시다.

위 그림은 endpoint가 /이면서, GET 요청을 받았을 때에, 사용하는 미들웨어입니다. 주목할 것은 파라미터 순서입니다. req, res는 우리가 잘 아는 요청/응답이며, next는 다음 컨베이어 벨트로 넘기는 작업을 합니다. (이 문서의 맨 위의 그림을 참고하세요.)
현재 미들웨어 내부에서는 아무런 일도 하고 있지 않습니다. 그저 next() 함수를 호출하여 다음 컨베이어 벨트로 넘길 뿐이죠.

이번엔 특정 endpoint가 아닌, 모든 요청에 대해서 미들웨어를 붙여봅시다. 이때에는 app.use라는 메소드를 이용합니다. 아래 코드를 직접 실행해보십시오. 모든 요청에 대해 LOGGED가 콘솔에 찍히는 것을 확인하세요.

const express = require('express');
const app = express();

const myLogger = function (req, res, next) {
  console.log('LOGGED'); // 이 부분을 req, res 객체를 이용해 고치면, 
  //여러분들은 모든 요청에 대한 로그를 찍을 수 있습니다.
  next();
};

app.use(myLogger);

app.get('/', function (req, res) {
  res.send('Hello World!');
});

app.listen(3000);

목표는 다음과 같이, 모든 요청에 대해 메소드와 url을 찍는 것입니다. 직접 도전해보세요.

case 4: 요청 헤더에 사용자 인증 정보가 담겨있는지 확인하고 싶을 때 (advanced challenges)

다음은 HTTP 요청에서 토큰(주로 사용자 인증에 쓰이며, 추후 Authentication 시간에 다룹니다)이 있는지 여부를 판단하여, 이미 로그인한 사용자일 경우 성공, 아닐 경우 에러를 보내는 미들웨어 예제입니다.

app.use((req, res, next) => {
  // 토큰 있니? 없으면 받아줄 수 없어!
  if(req.headers.token){
    req.isLoggedIn = true;
    next()
  } else {
    res.status(400).send('invalid user')
  }
})

보통 접근 권한이 없는 웹사이트에 로그인 없이 접근을 시도하면 서버로부터 로그인 창 등으로 되돌려 보내는 경우를 종종 경험하였을 것입니다. 이런 식으로 컨베이어 벨트에 올라온 요청이 미들웨어가 요구하는 조건에 맞지 않으면 불량품으로 판단하고 돌려보내도록 구현할 수도 있습니다.


해당 github 링크

Sprint 진행 후 느낀 점

  • 어느 회사를 들어가든지, 일단 무조건 API 문서부터 받아와야한다는 것을 알았다.

  • Express.js로 리펙토링을 하고나니, Express.js 이전으로 되돌아가고 싶지 않다.

profile
좋은 길로만 가는 "조은길"입니다😁

0개의 댓글