TIL #14 Mini Server & Chatterbox-Server Sprint

Joshua Song / 송성현·2019년 12월 9일
0

이머시브_16

목록 보기
16/29

Intro

지난번에 만들었던 chatterbox client 어플은 이미 만들어진 AWS 서버에서 챗들을 받아오고 또 거기에 보내 새로운 챗을 보내 만들어 주었다. 이번 스프린트는 localhost와 연결을 해서 Node.js를 사용한, 서버 측면의 어플을 만드는 것이다.

  • Nodemon 을 활용.
  • 브라우저가 서버와 어떻게 연결하는가
  • API 문서 작성
  • export 와 require을 활용해서 코드를 작성.

Advanced

  • use socket.io
  • use fs module to set up the static html.
  • use fs module to have the server store the message so messages are saved.

이미 코드 작성은 마쳤지만 복습의 시간을 가져 어떤 부분이 헷갈렸고 어려웠는지, 어떤 부분이 한번 더 복습이 필요한지 정리할 것이다.

Using Request Handler

  • BareMinimum requirement 는 http를 사용해서 서버를 구축해서 코드를 짜는 것이었다. 함수를 짠 후 export를 해서 basic-server.js에서 require로 가져와서 사용하는 그런 구조이다. 미니 서버를 이미 만들고 진행한 스프린트여서 그런가 생각보다 재밌고 흥미롭게 진행했다.
  • 기억이 남는 부분은, 일단 OPTIONS라는 Pre-flight CORS 가 날라오는 부분이었고 또 들어오는 요청마다 헤더를 검사하고, response를 돌려줄때도 cors를 설정해주고 돌려주는게 신기했다. writeHead를 통해 cors를 허락해준다.
  • Data를 처음에 받을 때 chunk로 받기 때문에 Buffer.concat(body).toString()아니면 일반 string에 더해서 변환을 해줘야 한다는 점이 신기했다. chunk로 받은 데이터는 이진법으로 변환되어 있는 string 값이여서 그걸 다시 처리할 수 있는 형식으로 바꿔줘야 되는게 신기했다.
  • if 와 else를 적절하게 사용해 GET일 때는 돌려주고, POST일 때는 저장해주는 개념은 생각보다 어렵지 않았다.
  • 내가 선택한 방법은 함수 외부에 id 변수를 만들어줘 POST를 처리할 때마다 id++로 아이디 값을 추가해주었고 date도 추가해주었다. 그리고 따로 배열을 가진 객체를 함수바깥에 두어 받은 메시지를 거기에 push해줌으로 저장을 해주었다.
  • 수월하게 할 수 있고 흐름도 잘 따라가서 advanced도 나중에는 구현할 수 있었다. fs module은 아래서 설명!
  • 흐름이 중요하다! request를 받아 처리 후 response로 돌려준다!
/*************************************************************

request handler 함수를 여기서 작성합니다.

reuqestHandler 함수는 이미 basic-server.js 파일에서 사용 했지만, 아직 작동하지 않습니다.

requestHandler 함수를 export 하여 basic-server.js 에서 사용 할 수 있게 하세요

**************************************************************/
var fs = require("fs");

let returnValue = {
  "results": [],
};
  
fs.readFile('./fsfile.json', 'utf8', function(err, data){
  if (err){
    return;
  }
  
  else if (data.length > 0 && typeof (JSON.parse(data)) === "object"){
      returnValue = JSON.parse(data);
     
  }
})


var id = 0;

const requestHandler = function(request, response) {
  const headers = defaultCorsHeaders;

  function change (basic, body){
    basic.username = body.username;
    basic.text = body.text;
    basic.roomname = body.roomname;
    basic.id = id;
    basic.date = new Date();
    return basic;
  }
  
 
  let body = [];
  let basicFormat = {
    "id": undefined,
    "username": undefined,
    "text": undefined,
    "roomname": undefined,
    "date": undefined
  }
  
  if (request.method === "OPTIONS"){
    response.writeHead(200, headers)
    response.end();
    return;
  }
  else if (request.method === "GET"){
    if (request.url ==="/classes/messages") {
      
      response.writeHead(200, headers)
      response.end(JSON.stringify(returnValue));
      }
      
    else {
      response.writeHead(404, headers)
      response.end("No such url")
    }
  }

  else if (request.method === "POST") {
    if (request.url === "/classes/messages" ) {
      request
        .on("data", chunk => {
          body.push(chunk);
        })
        .on("end", () => {
          body = Buffer.concat(body).toString()
          body = JSON.parse(body);
          returnValue.results.push(change(basicFormat, body));
          let number = id;
          id ++;
          fs.writeFile('./fsfile.json', JSON.stringify(returnValue), "utf8", function(err) {
            if (err){
              console.log("error")  
            }
            response.writeHead(201, headers);
            response.end(JSON.stringify({"id": number})); 
          });
          })
          
      }  
    
    else {
      response.writeHead(404, headers)
      response.end("No such url")
      
    } 
  }
  else {
    response.writeHead(200, headers)
    response.end("Only valid method is POST")
    
  }
  console.log(
    'Serving request type ' + request.method + ' for url ' + request.url
  );

 
  headers['Content-Type'] = 'text/plain';


  
};


const defaultCorsHeaders = {
  '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 // Seconds.
};

module.exports = requestHandler;

Using Express

  • http createserver로 서버함수를 구축한 후 다음 단계는 express라는, 엄청나게 편리한 모듈을 사용해 서버를 구축하는 것이었다. (갓프레스)...
  • 실제로 사용해보니...너무 편해서 http로 만든 서버 함수가, 그만큼 내가 들인 시간이 아깝...지 않고 좋은 걸 배웠다고 생각한다.
  • express 는 일단 middleware라는, 엄청 똑똑한사람들이 만든 중간 필터링 함수? 들을 사용할 수 있어서 웬만한 작업은 얘네들이 다 해주었다.
  • body-parse라는 middleware는 chunk된 data를 받아와 parse까지 완벽하게 구현해 내가 바로 사용할 수 있게 해주었고 cors라는 미들웨어는 Pre-flight 부터 일반 헤더에 있는 cors까지 내 설정에 맞추어 완벽하게 구현해 주었다.
  • get일때, post일때는 router를 통해 각각 무엇을 실행해 줄지만 함수를 짜주면 되었다. 진짜 편리하다.
  • 긴말 필요없다...코드 보고 길이의 차이를 느끼자.
const express = require("express");
const app = express();
const bodyParser = require('body-parser');
const cors = require('cors');
const fs = require('fs');


const PORT = process.env.NODE_ENV === 'production'? 3001:3002

const defaultCorsHeaders = {
    'origin': '*',
    'methods': 'GET, POST, PUT, DELETE, OPTIONS',
    'allowedHeaders': 'content-type, accept',
    'maxAge': 10, // Seconds.
    "optionsSuccessStatus": 200
};



let returnValue = {
    "results": [],
  };

app.use(express.static('static'));

app.use(cors(defaultCorsHeaders))

app.use(bodyParser.json())

fs.readFile('./fsfile.json', 'utf8', function(err, data){
    if (err){
      return;
    }
    
    else if (data.length > 0 && typeof (JSON.parse(data)) === "object"){
        returnValue = JSON.parse(data);
    }
  })

app.get('/', function(req, res) {
    res.sendFile("index.html");
})

app.get('/classes/messages', function (req, res){
  res.status(200).send(JSON.stringify(returnValue));
})

app.post('/classes/messages', function (req, res){
    req.body.id = returnValue.results.length;
    req.body.date = new Date();
    returnValue.results.push(req.body);
    fs.writeFile('./fsfile.json', JSON.stringify(returnValue), "utf8", function(err) {
        if (err){
          console.log("error")  
        }
    res.status(201).send(JSON.stringify(returnValue.results.indexOf(req.body)));
    });
})


app.listen(PORT, () => {
    console.log(`server listen on ${PORT}`)
})

Advanced

  • 이번 sprint의 advanced부분은 크게 socket.io사용과 fs를 사용해 정적 html과 정보저장을 구현하는 것인데 시간 상 나는 fs module을 배워서 사용했다.

fs (File-System) Module

  • 이름 그대로에서 볼 수 있듯 파일 처리와 관련된 모듈로 엄청 메소드가 많다. 하지만 내가 사용한 건 writeFile과 readFile이다.

writeFile

fs.writeFile('./fsfile.json', JSON.stringify(returnValue), "utf8", function(err) {
        if (err){
          console.log("error")  
        }
    res.status(201).send(JSON.stringify(returnValue.results.indexOf(req.body)));
    });
  • writeFile은 말그대로 파일을 만들어주는 method이다. 첫번째 인자가 만들어지는 파일의 주소, 두번째는 파일 안에 넣어줄 내용, "utf8"은 인코딩, 그리고 마지막 함수는 에러 감지와 파일 생성 후 실행해 줄 내용이다. 이번 함수는 Post를 받은 후 id 번호를 response로 보내주었다.

readFile

  • 이름 그대로에서 볼 수 있듯 파일을 읽는 메소드이다. 파일안의 내용을 계속 replace하며 업데이트 해줄 수 있다. 개꿀.
fs.readFile('./fsfile.json', 'utf8', function(err, data){
    if (err){
      return;
    }
    
    else if (data.length > 0 && typeof (JSON.parse(data)) === "object"){
        returnValue = JSON.parse(data);
    }
  })

첫번째 인자는 읽는 파일, 두번째는 인코딩, 세번째는 읽은 후 실행하는 함수이다. 함수를 보면 그냥 읽은 데이터의 상태를 확인 후 따로 저장해 놓는 객체를 파일에 계속 업데이트 해준다. 그러면 서버가 새로고침을 해도 fs 파일로 데이터를 저장해 놓았기 때문에 예전 챗들을 볼 수 있다.

마무리

  • 서버와 클라이언트 스프린트를 연달아서 하다보니 흐름을 생각보다 잘 이해할 수 있었다. 영상을 보고 이미지를 볼때는 그냥 추상적인 느낌이 강했는데 실제로 해보니...생각보다 말이 된다! 아는 선배의 말을 들어보면 7 레이어? 이런 깊은 이론으로 들어가면 더 복잡한데...지금 나는 초보니 이걸 구현한다는 점에 만족한다.

클라이언트가 요청을 보내야만 서버가 응답을 한다! 만약 서버가 응답을 보내길 원하면 socket을 구현해야 하는데...지금은 할 시간이 없다.

  • 클라이언트 <=> PRE-Flight <=> 서버.

헤더는 생각보다 많은 정보를 담고 매우 중요하다!

고생했다~

profile
Grow Joshua, Grow!

0개의 댓글