[Node.js] http 모듈로 웹 서버 만들기 (2)

http 모듈로 웹 서버 만들기 (2)

메모리 세션 구현해보기

쿠키는 값이 보이기 때문에 보안에 취약하다. –> 1차적으로 값을 안보이게 하기 위해 세션을 사용한다. (하지만, 이 또한 쿠키의 세션ID값이 유출된다면 개인정보가 유출될 가능성이 있다.)

const http = require("http");
const fs = require("fs");
const url = require("url");
const qs = require("querystring");

const parseCookies = (cookie = "") =>
  cookie
    .split(";")
    .map(v => v.split("="))
    .reduce((acc, [k, v]) => {
      acc[k.trim()] = decodeURIComponent(v);
      return acc;
    }, {});

const session = {};

http
  .createServer((req, res) => {
    const cookies = parseCookies(req.headers.cookie);
    if (req.url.startsWith("/login")) {
      const { query } = url.parse(req.url);
      const { name } = qs.parse(query);
      const expires = new Date();
      expires.setMinutes(expires.getMinutes() + 5);
      // new Date 앞에 + 를 붙이면 new Date().getTime() 과 같은 기능을 한다.
      // Unix Timestamp 값을 가져온다.
      const randomInt = +new Date();
      // randomInt 값을 통해 session에 저장된 객체에 접근할 수 있다.
      session[randomInt] = {
        name,
        expires
      };
      res.writeHead(302, {
        Location: "/",
        "Set-Cookie": `session=${randomInt}; Expires=${expires.toGMTString()}; HttpOnly; Path=/`
      });
      res.end();
    } else if (
      cookies.session &&
      // 유효기간이 지나지 않았을 때
      session[cookies.session].expires > new Date()
    ) {
      res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
      res.end(`${session[cookies.session].name}님 안녕하세요`);
    } else {
      fs.readFile("./server4.html", (err, data) => {
        if (err) {
          throw err;
        }
        res.end(data);
      });
    }
  })
  .listen(8084, () => {
    console.log("8084번 포트에서 서버 대기중입니다!");
  });

REST API의 개념

회원 정보와 같은 자원들(Resources)을 주소를 통해서 가져오는데, 그 주소를 구조화(조합) 하는 방법을 REST API 라고 한다.

  • GET - www.zerocho.com/users
    • 사용자를 가져오다.
  • POST - www.zerocho.com/users
    • 사용자를 등록하다.
  • PUT - www.zerocho.com/users/1
    • 전체를 바꾸다.
  • PATCH - www.zerocho.com/users/2
    • 나이, 성별 등 사용자 정보의 일부분을 바꾸다.
  • DELETE - www.zerocho.com/users/5
    • 사용자를 지우다.

HTTP 메서드(req.method)로 분기 처리하기

const http = require("http");
const fs = require("fs");

// 유저 정보를 담기 위한 메모리 변수, DB 대신 사용 (서버 재시작 시 날라감)
const users = {};

// createServer는 방문에 대한 이벤트 리스너 역할을 한다.
http
  .createServer((req, res) => {
    if (req.method === "GET") {
      if (req.url === "/") {
        return fs.readFile("./restFront.html", (err, data) => {
          if (err) {
            throw err;
          }
          res.end(data);
        });
      } else if (req.url === "/about") {
        return fs.readFile("./about.html", (err, data) => {
          if (err) {
            throw err;
          }
          res.end(data);
        });
      } else if (req.url === "/users") {
        return res.end(JSON.stringify(users));
      }
      // css, front-js 파일 등을 한번에 읽어오기 위한 코드
      return fs.readFile(`.${req.url}`, (err, data) => {
        if (err) {
          res.writeHead(404, "NOT FOUND");
          return res.end("NOT FOUND");
        }
        return res.end(data);
      });
    } else if (req.method === "POST") {
      if (req.url === "/users") {
        let body = "";
        req.on("data", data => {
          // post 요청으로 들어온 데이터 chunk를 빈 body 문자열에 합친다.
          body += data;
        });
        return req.on("end", () => {
          console.log("POST 본문(Body):", body);
          const { name } = JSON.parse(body);
          const id = +new Date();
          users[id] = name;
          res.writeHead(201, { "Content-Type": "text/html; charset=utf-8" });
          res.end("등록 성공");
        });
      }
    } else if (req.method === "PUT") {
      if (req.url.startsWith("/users/")) {
        // 해당 유저를 찾기 위해 주소에서 key 값을 꺼낸다.
        const key = req.url.split("/")[2];
        let body = "";
        req.on("data", data => {
          body += data;
        });
        return req.on("end", () => {
          console.log("PUT 본문(Body):", body);
          users[key] = JSON.parse(body).name;
          return res.end(JSON.stringify(users));
        });
      }
    } else if (req.method === "DELETE") {
      if (req.url.startsWith("/users/")) {
        const key = req.url.split("/")[2];
        delete users[key];
        return res.end(JSON.stringify(users));
      }
    }
    res.writeHead(404, "NOT FOUND");
    return res.end("NOT FOUND");
  })
  .listen(8085, () => {
    console.log("8085번 포트에서 서버 대기중입니다");
  });

라우터 리팩토링

/* 리팩토링 */
const router = {
  GET: {
    '/': (req, res) => {
      fs.readFile("./restFront.html", (err, data) => {
        if (err) {
          throw err;
        }
        res.end(data);
      });
    },
    '/users': (req, res) => {
      res.end(JSON.stringify(users));
    },
    // url  어디에도 안 걸릴 경우, 와일드카드에 걸리도록 해준다.
    '*': (req, res) => {
      fs.readFile(`.${req.url}`, (err, data) => {
        if (err) {
          res.writeHead(404, "NOT FOUND");
          return res.end("NOT FOUND");
        }
        return res.end(data);
      });
    },
  },
  POST: {
    '/users': (req, res) => {
      let body = "";
      req.on("data", data => {
        // post 요청으로 들어온 데이터 chunk를 빈 body 문자열에 합친다.
        body += data;
      });
      return req.on("end", () => {
        console.log("POST 본문(Body):", body);
        const { name } = JSON.parse(body);
        const id = +new Date();
        users[id] = name;
        res.writeHead(201, { "Content-Type": "text/html; charset=utf-8" });
        res.end("등록 성공");
      });
    },
  },
  PATCH: {
    ...
  },
  PUT: {
    '/users': (req, res) => {
      const key = req.url.split("/")[2];
      let body = "";
      req.on("data", data => {
        body += data;
      });
      return req.on("end", () => {
        console.log("PUT 본문(Body):", body);
        users[key] = JSON.parse(body).name;
        return res.end(JSON.stringify(users));
      });
    },
  },
  DELETE: {
    '/users': (req, res) => {
      const key = req.url.split("/")[2];
      delete users[key];
      return res.end(JSON.stringify(users));
    },
  }
}

const server = http.createServer((req, res) => {
  const matchedUrl = router[req.method][req.url]
  // matchedUrl에 걸릴 경우 matchedUrl(req,res) 실행
  // 와일드카드에 걸리게 하려면 matchedUrl이 undefined일 때 router 객체의 asterisk를 찾아서 실행해준다.
  (matchedUrl || router[req.method]['*'])(req, res);
}).listen(8085, () => {
  console.log("8085번 포트에서 서버 대기중입니다");
})

댓글남기기