Nodejs Chap4-1 요청과 응답

jade·2025년 2월 20일

Node.js

목록 보기
5/11

1. 서버와 클라이언트

1) 서버와 클라이언트의 관계

  • 클라이언트가 서버로 "요청request"을 보냄
  • 서버는 요청을 처리
  • 처리 후 클라이언트로 "응답response"을 보냄

2) HTTP

const http = require('http');
const server = http.createServer((req, res) => {
    res.write('<h1>hello node</h1>');
    res.write('<h2>hello node</h2>');
    res.write('<h3>hello node</h3>');
})
    .listen(8080);

server.on('listening', () => {
    console.log('8080포트에서 서버 대기 중입니다.');
});
server.on('error', () => {
    console.log(error);
});

localhost8080에 들어가면 hello node 가 나옴!
localhost는 컴퓨터 내부 주소로 외부에서는 접근이 안됨..!

2. fs로 HTML 읽어 제공하기

const http = require('http');
const fs = require('fs').promises;

const server = http.createServer(async(req, res) => {
    try {
        res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
        const data = await fs.readFile('./chap4/server2.html');
        res.end(data);
    }
    catch (error) {
        console.log(error);
        res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
        res.end(error.message);
    }
})
    .listen(8080);
    

server.on('listening', () => {
    console.log('8080포트에서 서버 대기 중입니다.');
});
server.on('error', (error) => {
    console.log(error);
});

3. REST API 서버

서버에 요청을 보낼 때 주소를 통해 요청의 내용을 표현
-> 서버가 이해하기 쉬운 주소로 보내는게 좋음
REST API: 주소를 정하는 규칙

  • ex) /post : 게시글과 관련된 요청

1) HTTP 프로토콜

RESTful: REST API를 사용한 주소 체계를 이용하는 서버

GET: 서버 자원을 가져와야 할 때
POST: 서버에 자원을 새로 등록하고자 할때
PUT: 서버의 자원을 요청에 들어잇는 자원으로 치환하려 할때(전체수정)
PATCH: 서버 자원의 일부만 수정하고자 할때(일부수정)
DELETE: 서버의 자원을 삭제하고자 할때

2) CRUD REST API 서버 만들어보기

이 서버의 설명

  • GET / → restFront.html 제공
  • GET /about → about.html 제공
  • GET /users → 저장된 사용자 목록(JSON) 반환
  • POST /user → 새로운 사용자 추가
  • PUT /user/:id → 사용자 정보 수정
  • DELETE /user/:id → 사용자 삭제
  • 파일 제공 기능 → 정적 파일 요청 처리
  • 존재하지 않는 라우트 요청 시 404 NOT FOUND
  • 서버 오류 시 500 INTERNAL SERVER ERROR

writeHead : http 서버에서 응답헤더를 설정

  • http 상태코드와 응답헤더를 설정한 후 본문을 전송할 준비를 함
  • res.end()로 본문을 전송하여 응답 완료
const http = require('http');
const fs = require('fs').promises;
const path = require('path');

const users = {}; // 데이터 저장용

//서버 생성, req: request, res: respond
http.createServer(async (req, res) => {
  try {
    if (req.method === 'GET') {
      //GET /: restFront.html 파일을 읽어와 응답
      //200 응답코드와 함께 html 반환
      if (req.url === '/') {
        const data = await fs.readFile(path.join(__dirname, 'restFront.html'));
        res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
        //200,201 -> 성공적으로 요청에 응답
        //404 -> 요청에 응답 실패 NOT FOUND
        return res.end(data);
      }
      //GET /about: about.html을 읽어와 응답
      else if (req.url === '/about') {
        const data = await fs.readFile(path.join(__dirname, 'about.html'));
        res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
        return res.end(data);
      }
      //GET/ users: users 객체를 JSON 형식으로 응답
      else if (req.url === '/users') {
        res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
        return res.end(JSON.stringify(users));
      }
      // /도 /about도 /users도 아니면
      try {
        const data = await fs.readFile(path.join(__dirname, req.url));
        return res.end(data);
      } catch (err) {
        // 주소에 해당하는 라우트를 못 찾았다는 404 Not Found error 발생
      }
    }
    //POST 요청 처리: 사용차 추가
    else if (req.method === 'POST') {
      // POST /user
      if (req.url === '/user') {
        let body = '';
        // 데이터를 스트림 형태로 받아 body 변수에 저장
        req.on('data', (data) => {
          body += data;
        });
        // 요청의 body를 다 받은 후 실행됨
        // 받은 JSON 데이터를 파싱하여 사용자 객체에 추가
        return req.on('end', () => {
          console.log('POST 본문(Body):', body);
          const { name } = JSON.parse(body);
          const id = Date.now();
          users[id] = name;
          res.writeHead(201, { 'Content-Type': 'text/plain; charset=utf-8' });
          res.end('등록 성공');
        });
      }
    }
    //PUT /user:id : 해당 사용자의 정보 수정
    else if (req.method === 'PUT') {
      if (req.url.startsWith('/user/')) {
        const key = req.url.split('/')[2];
        // /user/12345 같은 요청에서 12345를 추출하여 사용자의 키로 사용
        // 수정할 데이터(body)를 받습니다.
        let body = '';
        req.on('data', (data) => {
          body += data;
        });
        return req.on('end', () => {
          console.log('PUT 본문(Body):', body);
          users[key] = JSON.parse(body).name;
          res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
          return res.end(JSON.stringify(users));
        });
      }
    }
    else if (req.method === 'DELETE') {
      if (req.url.startsWith('/user/')) {
        //해당 키를 가진 사용자를 삭제. 삭제 후 응답
        const key = req.url.split('/')[2];
        delete users[key];
        res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
        return res.end(JSON.stringify(users));
      }
    }
    //주소에 해당하는 라우트를 하나도 찾지 못했을 때 -> 404 NOT FOUND
    res.writeHead(404);
    return res.end('NOT FOUND');
  } catch (err) {
    console.error(err);
    res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });
    res.end(err.message);
  }
})
  .listen(8082, () => {
    console.log('8082번 포트에서 서버 대기 중입니다');
  });

4. 쿠키, 세션

1) 쿠키

요청의 단점: 누가 요청을 보냈는지 알수가 없음

  • IP주소와 브라우저 정보 정도만 알 수 있음
  • "누가" 요청을 보냈는지를 알려면 쿠키, 세션 필요

쿠키: 쿠키명=쿠키값 의 쌍

  • ex) name=jade
  • 매 요청마다 쿠키를 서버에 동봉해서 보냄
    서버가 응답할 때도 쿠키랑 같이 응답
  • 서버는 쿠키를 읽어서 누구인지 파악

쿠키 옵션

  • Expires, Max-age, Domain(쿠키가 전송될 도메인을 특정), Path(쿠키가 전송될 URL), Secure, HttpOnly

예제코드

  • 쿠키문자열을 객체로 변환(parseCookies)
  • 주소가 /login, / 인경우로 나눔
  • /login : 이름을 쿠키로 5분간 저장, / 으로 redirect
  • / : 쿠키가 있는지 없는지 확인
    있으면 환영 인사, 없으면 로그인 페이지 띄움
const http = require('http');
const fs = require('fs').promises;
const path = require('path');
//쿠키문자열을 객체{name:"value"}로 변환
const parseCookies = (cookie = '') => 
//cookie가 null/undefined라면 빈문자열
  cookie  //'cookie1=1234; cookie2=5678'
    .split(';') //["cookie1=1234", "cookie2=5678"]
    .map(v => v.split('=')) //[["cookie1", "1234"], [" cookie2", "5678"]]
    .reduce((acc, [k, v]) => {
      acc[k.trim()] = decodeURIComponent(v);
      return acc;
      //reduce() 를 이용해 {key:value}형태의 객체 생성
      //k.trim(): 공백 제거
      //decodeURLComponent(v): URL 인코딩된 값을 디코딩
    }, {}); //결과:{cookie1: "1234"},{cookie2: "5678"}

http.createServer(async (req, res) => {
  const cookies = parseCookies(req.headers.cookie); // { mycookie: 'test' }
  // 주소가 /login으로 시작하는 경우(로그인버튼을 눌렀을때)
  if (req.url.startsWith('/login')) {
    const url = new URL(req.url, 'http://localhost:8084');
    const name = url.searchParams.get('name');
    const expires = new Date();
    // 쿠키 유효 시간을 현재시간 + 5분으로 설정
    expires.setMinutes(expires.getMinutes() + 5);
    res.writeHead(302, { //302-> redirection
      Location: '/',
      'Set-Cookie': `name=${encodeURIComponent(name)}; Expires=${expires.toGMTString()}; HttpOnly; Path=/`,
    });
    res.end();
  }
    //주소가 / 이면서 쿠키가 있는 경우
  else if (cookies.name) {
    res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
    res.end(`${cookies.name}님 안녕하세요`);
  }
  else {//주소가 / 이면서 쿠키가 없는 경우
    try {
      const data = await fs.readFile(path.join(__dirname, 'cookie2.html'));
      res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
      res.end(data);
    } catch (err) {
      res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });
      res.end(err.message);
    }
  }
})
  .listen(8084, () => {
    console.log('8084번 포트에서 서버 대기 중입니다!');
  });

2) 세션

쿠키의 정보는 노출되고 수정되는 위험이 있음 -> 세션 이용

세션 원리
중요한 정보는 서버에서 관리하고 클라이언트에는 세션 키만 제공
서버에 세션객체 생성 후 키(uniquelnt)를 만들어 속성명으로 이용
속성 값에 정보를 저장하고 uniquelnt를 클라이언트에 제공
6장에 더 깊게 다룰 예정

const http = require('http');
const fs = require('fs').promises;
const path = require('path');

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

const session = {};

http.createServer(async (req, res) => {
  const cookies = parseCookies(req.headers.cookie);
  if (req.url.startsWith('/login')) {
    const url = new URL(req.url, 'http://localhost:8085');
    const name = url.searchParams.get('name');
    const expires = new Date();
    expires.setMinutes(expires.getMinutes() + 5);
    //**여기부터 세션**//
    const uniqueInt = Date.now();
    session[uniqueInt] = {
      name,
      expires,
    };
    res.writeHead(302, {
      Location: '/',
      'Set-Cookie': `session=${uniqueInt}; Expires=${expires.toGMTString()}; HttpOnly; Path=/`,
    });
    res.end();
  // 세션쿠키가 존재하고, 만료 기간이 지나지 않았다면
  } else if (cookies.session && session[cookies.session].expires > new Date()) {
    res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
    res.end(`${session[cookies.session].name}님 안녕하세요`);
  } else {
    try {
      const data = await fs.readFile(path.join(__dirname, 'cookie2.html'));
      res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
      res.end(data);
    } catch (err) {
      res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });
      res.end(err.message);
    }
  }
})
  .listen(8085, () => {
    console.log('8085번 포트에서 서버 대기 중입니다!');
  });

0개의 댓글