6 분 소요


▶ 요청(request)과 응답(response) 이해하기

서버에선 요청을 받는 부분과 응답을 보내는 부분이 있어야 한다.

클라이언트로부터 요청이 왔을 때 어떤 작업을 수행할지 이벤트 리스너를 미리 등록해 놔야한다.

⭐아래 링크 꼭!!! 참고해서 공부하기 (완벽히 이해하기)

HTTP 프로토콜

▷ 노드 서버 만들기

// http라는 객체를 생성하여 서버의 프로토콜을 http로 한다.
// requir를 통해 모듈을 사용할 수 있으며  c언어의 #include와 같다.
// http라는 모듈이 가진 라이브러리를 http에 담아 사용한다.
const http = require("http");

// 외부 서버가 아닌 host의 주소를 localhost(자기 자신)로 한다.
/* const hostname = '127.0.0.1'; //loop back (내부에서 루프를 도는 것,
즉 자기 자신(서버) 그 자체를 말한다.) */

const hostname = "localhost";

// server 객체에 이벤트를 연경한다.
/* HTTP Status Code 요청의 결과가 어떻게 되었는지를 알려주는 것 (요청(req)에 대한 상태)
ex) 요청처리중, 200 요청 성공, 404 요청오류
*/
const port = 3000; // 클라이언트가 http://localhost:3000 주소로 접속할 수 있다.
const server = http.createServer((req, res) => {
  res.statusCode = 200; // 요청 성공

  // charset=utf-8" 한국어 사용
  res.setHeader("Content-Type", "text/html; charset=UTF-8");
  res.write("Hello!!!!!!!!!!!!");
  res.end("반가웠어!!!");
});

// listen을 통해 서버는 클라이언트가 언제 요청을 보낼지 듣고 있다.
// 즉,listen을 통해 클라이언트가 언제 요청을 보내는지 감시(모니터링)한다.
server.listen(port, hostname, () => {
  console.log("Server running at http://${hostname}:${port}/");
});

// 서버 오류 발생시 오류 문구 출력 코드 추가
server.on("error", (error) => {
  console.error("오류입니다", error);
});
  • 아래와 같이 출력
  • http 서버가 있어야 웹 브라우저의 요청을 처리할 수 있으므로 http 모듈을 사용했다.
  • http 모듈에는 createServer 메서드가 있다.
  • 서버에 접속할 때 두 개의 중요한 접속 정보 : ip 주소, port 주소

질문! text/html과 text/plain은 무슨 차이점이 있을까?

  • text/plain은 평문 텍스트 유형이므로 웹 페이지의 HTML 마크업이 아닌 이미지, CSS 코드 또는 HTML 태그가 없는 일반적인 텍스트를 반환하는 경우에 사용되는 반면,

  • text/html은 웹 페이지와 같은 구조화된 문서를 반환한다.


위 코드를 다음과 같이 축약할 수 있다.

const http = require("http");

const server = http.createServer((req, res) => {
  res.setHeader(200, "Content-Type", "text/html; charset=UTF-8");
  res.write("<h1>Hello!!!!!!!!!!!!</h1>");
  res.end("반가웠어!!!");
});

server.listen(3000); //3000포트만 관심있다(듣는다)

// listening, error 이벤트 리스너 붙이기
server.on("listening", () => {
  console.log("3000 포으테서 대기 중입니다!");
});

server.on("error", (error) => {
  console.error("오류입니다", error);
});

★listen(3000);


▶ localhost와 포트

port localhost는 컴퓨터 내부 주소 -> 외부에서 접근 불가능 ★루프백주소: 밖으로 나가는게 아니고, 다시 나에게 돌아온다는 듯 ★localhost와 127.0.0.1은 동일하다.


▶ server 객체 메서드

  • http 모듈의 createServer() 메서드를 사용하면 server 객체를 생성할 수 있다.

(! server 객체는 HTTP 서버를 나타낸다.)

  • server 객체의 .listen() 메서드를 호출하면, 해당 포트에서 요청을 수신하는 HTTP 서버가 생성 → 이 객체를 통해 서버 측에서 클라이언트에게 응답을 보낼 수 있다. | server 객체의 메서드 | 설명 | | ————————– | ——— | | llisten(port[, callback])) | 서버 실행 | | close() | 서버 종료 |
const http = require("http");

// 웹 서버 생성
const server = http.createServer();

// 웹 서버 실행
// server.listen(port?:number, hostname?:string, backlog?:number, listeningListener:function)
server.listen();


▶ server 객체의 이벤트

server 객체의 이벤트 설명
server.on(‘request’, callback) 클라이언트가 요청할 때 발생하는 이벤트
server.on(‘connection’, callback) 클라이언트가 접속할 때 발생하는 이벤트
server.on(‘close’, callback) 서버가 종료될 떄 발생하는 이벤트
server.on(‘checkContinue’, callback) 클라이언트가 지속적인 연결을 하고 있을 때 발생하는 이벤트
server.on(‘upgrade’, callback) 클라이언트가 HTTP 업그레이드를 요청할 때 발생하는 이벤트
server.on(‘clientError’, callback) 클라이언트에서 오류가 발생할 때 발생하는 이벤트
//모듈을 추출
const http = require("http");

//server 객체를 생성
const server = http.createServer();

//server 객체에 이벤트를 연결
server.on("request", function () {
  console.log("클라이언트가 요청 중입니다.");
});
server.on("connection", function () {
  console.log("클라이언트 접속하였습니다.");
});
server.on("close", function () {
  console.log("서버가 종료되었습니다.");
});

// 서버 오류 발생시 오류 문구 출력 코드 추가
server.on("error", (error) => {
  console.error("오류입니다", error);
});

//listen() 메서드를 실행
server.listen(1016, function () {
  console.log("서버가 구동중입니다.");
});


▶ response 객체

  • 클라이언트에 웹 페이지를 제공하기 위해response 객체를 사용
  • response 객체는request 이벤트리스너의 두 번째 매개변수로 전달되는 객체
response 객체의 메서드 설명
writeHead(statusCode, object) 응답 헤더를 작성
end([data], [encoding]) 응답 본문을 작성
require("http")
  .createServer(function (request, response) {
    //웹 서버를 생성하고 모듈을 추출합니다.
    //응답합니다.
    response.setHeader("Content-Type", "text/html"); //향후 검색 및 수정 가능성이 있는 경우 response.setHeader()대신 response.writeHead()
    response.writeHead(200, { "Content-Type": "text/html" }); //writeHead는 한 번만 호출되어야 하며 end()가 호출되기 전에 호출되어야 함. 상태 코드는와 같은 3 자리 HTTP 상태 코드 404입니다.
    response.write(); //이것은 응답 본문의 청크를 보냅니다. 이 방법은 신체의 연속적인 부분을 제공하기 위해 여러 번 호출 될 수 있습니다.
    response.end("<h1>Hello Web Server with Node.js</h1>"); //데이터가 로드되었음을 서버에 알림
  })
  .listen(1016, function () {
    //웹 서버를 실행합니다.
    console.log("Server Running at localhost:1016");
  });

질문! response.write()와 response.end() 차이점

  • response.write()는 여러 번 호출될 수 있기 때문에 큰 데이터를 보낼 때 사용

  • response.end()는 한 번 호출될 수 있기 때문에 데이터 전송을 완료하고 응답 종료할 대 사용


▶ html 읽어서 전송하기 (이해x)

write과 end에 문자열을 넣는 것은 비효율적 → 타이핑이 힘들기 때문

따라서 fs 모듈로 html 읽어서 전송하자 (fs모듈: FileSystem의 약자로 파일 처리와 관련된 모듈)

data로 html파일 읽어서 data를 돌려주는게 이 코드의 핵심 try catch구문: try블럭에서 error가 발생할 경우 error를 catch블럭으로 흐름이 넘어간다. (스크립트가 죽는걸 방지)

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

http
  .createServer(async (req, res) => {
    try {
      const data = await fs.readFile("./server2.html");
      res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
      res.end(data);
    } catch (err) {
      console.error(err);
      res.writeHead(500, { "Content-Type": "text/plain; charset=utf-8" });
      res.end(err.message);
    }
  })
  .listen(8081, () => {
    console.log("8081번 포트에서 서버 대기 중입니다!");
  });


▷ 여러 개의 서버 실행하기

createServer을 여러번 호출하면 된다.

단, 두 서버의 포트를 다르게 지정해야 한다.


▷ 서버 실행하기

  • Ctrl + 또는 Ctrl + j로 터미널 실행한 후node 파일명` 입력해 서버 실행하기
  • 터미널 말고 파일이 폴더에 접근해 cmd를 사용하여 node 파일명 입력해 서버 실행이 가능하다.


▷ 서버 닫기

터미널에 exit 입력하면 된다.




▶ REST API

REST API 링크 들어가서 꼭 이해하기!!!!


▷ REST 서버 만들기

restFront.js

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

// 데이터 저장용 , 서버 메모리 확보(Database 용도... 메모리로 대체)
const users = {};

http
  .createServer(async (req, res) => {
    try {
      console.log(req.method, req.url);
      if (req.method === "GET") {
        if (req.url === "/") {
          // 이 파일을 읽을거야, 결과를 돌려줄거야 라는 의미
          const data = await fs.readFile(
            path.join(__dirname, "restFront.html")
          );
          res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
          return res.end(data);
        } 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);
        } else if (req.url === "/users") {
          res.writeHead(200, {
            "Content-Type": "application/json; charset=utf-8",
          });
          return res.end(JSON.stringify(users)); //JSON포멧의 스트링변환처리
        }
        // /도 /about도 /users도 아니면
        try {
          const data = await fs.readFile(path.join(__dirname, req.url));
          return res.end(data);
        } catch (err) {
          // 주소에 해당하는 라우트를 찾지 못했다는 404 Not Found error 발생
        }
      } else if (req.method === "POST") {
        if (req.url === "/user") {
          let body = "";
          // 요청의 body를 stream 형식으로 받음
          req.on("data", (data) => {
            body += data;
          });
          // 요청의 body를 다 받은 후 실행됨
          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("등록 성공");
          });
        }
      } else if (req.method === "PUT") {
        if (req.url.startsWith("/user/")) {
          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;
            res.writeHead(200, {
              "Content-Type": "application/json; 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": "application/json; charset=utf-8",
          });
          return res.end(JSON.stringify(users));
        }
      }
      res.writeHead(404);
      return res.end("NOT FOUND");
    } catch (err) {
      console.error(err);
      res.writeHead(500);
      res.writeHead(500, { "Content-Type": "text/plain; charset=utf-8" });
      res.end(err);
    }
  })
  .listen(8082, () => {
    console.log("8082번 포트에서 서버 대기 중입니다");
  });

코드설명

  • 코드를 보면 req.method로 HTTP 요청 메서드를 구분하고 있다. 메서드가 GET이면 다시 req.url로 요청 주소를 구분한다.

  • 주소가 /일 때는 restFront.html을 제공하고, 주소가 /about이면 about.html 파일을 제공하고 이외의 경우에는 주소에 적힌 파일을 제공한다..

  • /restFront.js라면 restFront.js 파일을 제공하고 /restFront.css라면 restFront.css 파일을 제공한다.

  • 만약 존재하지 않는 파일을 요청했거나 GET 메서드 요청이 아닌 경우라면 404 NOT FOUND 에러가 응답으로 전송된다.

  • 응답 과정 중에 예기치 못한 에러가 발생한 경우에는 500 에러가 응답으로 전송된다(실무에서는 500을 전송하는 경우는 드물다).




📎참조

Node.js HTTP 모듈

댓글남기기