본문 바로가기

Node.js

Node.js(6) - HTTP, Node.js 내부 카테고리, file system, HTML 불러오기

728x90
반응형

1) Node.js

   1-1) HTTP Protocol

   1-2) Node.js 내부 카테고리

   1-3) file system

   1-4) HTML 불러오기

 

 

 

 

 

1) Node.js

1-1) HTTP Protocol
'React'가 개발('meta'에서 개발함!)되면서 React로 웹 페이지를 많이 구현하게 되었고, 이로 인해 프론트엔드 개발자가 생겨났다!

※ API : 내 서버가 아닌 다른 사람의 서버에 데이터를 요청해서 가져오는 것

HTML을 어떻게 표현하나? --> 2가지 형태로 표현 가능하다!!

 

 

 

1-2) Node.js 내부 카테고리
- 내장객체

    - global

        - process
        - Buffer
        - __dirname
        - __filename
        - module
        - require

 

- 내장모듈

    - path
    - net
    - fs(file system)

 

외장모듈
    - express

 

- Javascript 개념
     - Node.js는 비동기 코드가 많기 때문에 callback 함수를 많이 사용함!!

 

 

 

1-3) file system(파일 시스템)
Node.js는 컴퓨터를 조작하는 언어이다!
즉, Node.js는 컴퓨터를 조작할 수 있기 때문에 서버를 만들 수 있다!!

const fs = require('fs');
fs.[method]; // fs 메서드를 사용하기 위한 문법 형태

fs.readFile();
fs.readFileSync();

 

 

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    hello world!!!
  </body>
</html>

 

fileSys.js

const fs = require("fs"); // fs : 파일 시스템을 가져오기 위한 내장모듈
const path = require("path");

// __dirname : /Users/hwangsangbeom/Documents/workspace/Nodejs/221212/filesystem + "/src/index.html"
// __filename : /Users/hwangsangbeom/Documents/workspace/Nodejs/221212/filesystem/fileSys.js

const filename = path.join(__dirname, "./src/index.html");
console.log(filename);

// "fs.readFile()"은 기본적으로 비동기로 실행됨!!(파일과 관련된 것은 기본적으로 다 비동기이다!)
// 첫번째 인자값: 파일경로 + 파일명
// 두번째 인자값: 인코딩(옵션에 해당하기에 안넣어도 됨!!)
// 세번째 인자값: 콜백함수(이 파일을 성공적으로 읽었을 때 실행할 파일이 여기서 말하는 콜백함수이다!)
// 비동기는 background에 들어가기 때문에 조작이 어렵다!!
fs.readFile(filename, "utf8", (error, data) => {
  if (error) console.log(error);
  console.log(data);
});

// "fs.readFileSync()"는 동기로 처리됨!!
// file을 읽을 경우에는 결국 "Buffer"로 읽음(즉, 아래 코드에서 "utf8" 인코딩 부분을 없애면 Buffer 형태로 file을 읽음!!)
const data = fs.readFileSync(filename, "utf8");
console.log("sync : ", data);

console.log("hello world!");

// 우리가 파일을 읽어올 때는 "fs.readFileSync()"를 사용해야 함!
// >> 이는 파일을 정확히 읽고 난 다음에 이를 조작한 후 데이터를 보내주어야 하기 때문!!

 

 

server.js

const net = require("net");
const resFn = require("./lib/res");
const reqFn = require("./lib/req");
const PORT = process.env.SERVER_PORT || 3000;
const HOST = process.env.SERVER_HOST || "127.0.0.1";

const server = net.createServer((client) => {
  client.setEncoding("utf8");

  client.on("data", (chunk) => {
    // "chunk"는 client에서 보낸 메시지를 담고 있음!!
    // console.log(chunk);
    const res = resFn(client); // 여기서 res의 데이터 타입은 "객체(Object)"이며, 해당 객체의 속성은 send, sendFile로 2개이다!!
    const req = reqFn(chunk); // chunk는 request message로 데이터 타입은 "string"이다!!
    console.log(req);
    // @TODO : request message 파싱
    // @TODO : response message 만들기

    res.send("<h1>응답~~~</h1>");
  });
});

server.on("connection", () => {
  console.log("connected to client");
});

server.listen(PORT, HOST, () => {
  console.log("server start");
});

 

res.js

const message = (content) => {
  const body = Buffer.from(content);

  return `HTTP/1.1 200 OK
Connection: Close
Content-Type: text/html; charset=UTF-8
Content-Length: ${body.length}

${body.toString()}`;
};

// 요청을 간단하게 만들어주는 함수
module.exports = (client) => {
  return {
    send: (body) => {
      const response = message(body);
      client.write(response);
    },
    sendFile: () => {},
  };
};

 

req.js

const msg = `GET /user?name=sangbeom&age=27 HTTP/1.1
Host: localhost:3000
Connection: keep-alive
sec-ch-ua: "Not?A_Brand";v="8", "Chromium";v="108", "Google Chrome";v="108"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "macOS"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Purpose: prefetch
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7

{
    "name":"sangbeom",
    "age":27
}`;

const getQuery = (queryString) => {
  if (queryString === undefined) return null;
  // 코드 목표 : 'name=sangbeom&age=27' --> {name:'sangbeom', age:27}로 바꾸는 것!!
  // 1) split 메서드를 통해 "&" 기준으로 구분하여 배열을 만듦 : 'name=sangbeom&age=27' --> ['name=sangbeom', 'age=27']
  // 2) map 메서드(요소 내용을 변경하는 함수)를 통해 "="을 기준으로 한 split 메서드를 해당 배열의 각 요소에 적용함 : ['name=sangbeom', 'age=27'] --> [['name', 'sangbeom'], ['age', 27]]
  // 3) reduce 메서드를 통해 배열을 객체로 변환함 : [['name', 'sangbeom'], ['age', 27]] --> {name:'sangbeom', age:'27'}
  return queryString
    .split("&")
    .map((v) => v.split("="))
    .reduce((acc, value) => {
      const [key, val] = value;
      acc[key] = val; // {name:'sangbeom', age:'27'}
      return acc;
    }, {});
};

const getMessage = (line) => {
  let flag = false;
  let body = "";
  for (const key in line) {
    if (flag)
      body = line
        .splice(key)
        .map((v) => v.trim())
        .join("");
    if (line[key] === "") flag = true;
  }
  line.pop();
  // ['Host: localhost:3000', 'Connection: keep-alive'] --> [[Host, localhost:3000], [Connection, keep-alive]]
  const headers = line
    .map((v) => v.split(":"))
    .reduce((acc, value) => {
      const [key, val] = value;
      acc[key] = val;
      return acc;
    }, {});

  return [headers, body]; // getMessage 함수의 return 값은 "배열"이다!!
};

const parser = (message) => {
  // string --> object
  // @TODO : start line 작업
  // @TODO : header, body 분리하기
  // @TODO : header, body 객체 만들기

  // message : request message의 내용이 담겨 있음!!(message의 데이터 타입: string)
  const header = message.split("\r\n"); // 해당 코드를 통해 우선 데이터 타입을 배열([])로 만듦!!
  const [method, url, version] = header.shift().split(" "); // ['GET', '/user?name=sangbeom&age=27', 'HTTP/1.1']
  const [path, queryString] = url.split("?"); // ['/user', 'name=sangbeom&age=27']
  const query = getQuery(queryString);

  const [headers, body] = getMessage(header); // header의 데이터 타입은 "배열"이다!!

  return { method, url, version, path, queryString, query, headers, body };
};

// const result = parser(msg); // msg의 데이터 타입은 "string"이다!!
// console.log(result);
module.exports = parser;

// 많은 양의 작업을 할 시에는 return을 먼저 정하고 작업하는 것이 좋음!!

 

req.js(body의 내용까지 처리한 코드 버전)

const msg = `GET /user?name=sangbeom&age=27 HTTP/1.1
Host: localhost:3000
Connection: keep-alive
sec-ch-ua: "Not?A_Brand";v="8", "Chromium";v="108", "Google Chrome";v="108"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "macOS"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Purpose: prefetch
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7
Content-Type: application/json

{
    "name":"sangbeom",
    "age":27
}`;

// header의 "Content-Type" 속성은 body 내용의 데이터 타입을 알려주기 위해 필요함!!
// Content-Type: application/json --> JSON(자바스크립트 객체)으로 실행시키라는 의미
// API(내 백엔드에서 다른 백엔드에 요청을 보내서 데이터를 가져오는 행위) 통신할 때는 대부분 JSON 형태로 데이터를 주고 받는다!!
// Content-Type: application/x-www-form-urlencoded --> queryString으로 실행시키라는 의미

const getQuery = (queryString) => {
  if (queryString === undefined) return null;
  // 코드 목표 : 'name=sangbeom&age=27' --> {name:'sangbeom', age:27}로 바꾸는 것!!
  // 1) split 메서드를 통해 "&" 기준으로 구분하여 배열을 만듦 : 'name=sangbeom&age=27' --> ['name=sangbeom', 'age=27']
  // 2) map 메서드(요소 내용을 변경하는 함수)를 통해 "="을 기준으로 한 split 메서드를 해당 배열의 각 요소에 적용함 : ['name=sangbeom', 'age=27'] --> [['name', 'sangbeom'], ['age', 27]]
  // 3) reduce 메서드를 통해 배열을 객체로 변환함 : [['name', 'sangbeom'], ['age', 27]] --> {name:'sangbeom', age:'27'}
  return queryString
    .split("&")
    .map((v) => v.split("="))
    .reduce((acc, value) => {
      const [key, val] = value;
      acc[key] = val; // {name:'sangbeom', age:'27'}
      return acc;
    }, {});
};

const bodyParser = (body, type) => {
  // type의 데이터 타입은 "string"이다!!
  if (type === undefined) return null;
  if (type.indexOf("application/json") !== -1) return JSON.parse(body);
  if (type.indexOf("application/x-www-form-urlencoded") !== -1)
    return getQuery(body);
  return body;
};

const getMessage = (line) => {
  let flag = false;
  let body = "";
  for (const key in line) {
    if (flag)
      body = line
        .splice(key)
        .map((v) => v.trim())
        .join("");
    if (line[key] === "") flag = true;
  }
  line.pop();
  // ['Host: localhost:3000', 'Connection: keep-alive'] --> [[Host, localhost:3000], [Connection, keep-alive]]
  const headers = line
    .map((v) => v.split(":"))
    .reduce((acc, value) => {
      const [key, val] = value;
      acc[key] = val;
      return acc;
    }, {});

  body = bodyParser(body, headers["Content-Type"]); // string 데이터를 객체({})로 바꾸려 함!!
  return [headers, body]; // getMessage 함수의 return 값은 "배열"이다!!
};

const parser = (message) => {
  // string --> object
  // @TODO : start line 작업
  // @TODO : header, body 분리하기
  // @TODO : header, body 객체 만들기

  // message : request message의 내용이 담겨 있음!!(message의 데이터 타입: string)
  const header = message.split("\r\n"); // 해당 코드를 통해 우선 데이터 타입을 배열([])로 만듦!!
  const [method, url, version] = header.shift().split(" "); // ['GET', '/user?name=sangbeom&age=27', 'HTTP/1.1']
  const [path, queryString] = url.split("?"); // ['/user', 'name=sangbeom&age=27']
  const query = getQuery(queryString);

  const [headers, body] = getMessage(header); // header의 데이터 타입은 "배열"이다!!

  return { method, url, version, path, queryString, query, headers, body };
};

// const result = parser(msg); // msg의 데이터 타입은 "string"이다!!
// console.log(result);
module.exports = parser;

// 많은 양의 작업을 할 시에는 return을 먼저 정하고 작업하는 것이 좋음!!

 

 

+) reduce 메서드(배열 메서드) 정리

// reduce는 Javascript의 배열(Array) 메서드이다!!
// reduce는 return 값이 어떠한 데이터 타입으로도 변환이 가능하다!!
// 배열에서 다른 데이터 타입으로 바꾸고 싶을 때 사용하는 메서드이다!!
// array -> number, array -> string, array -> object, array -> ...
// 주로 배열에서 객체로 만들 때 reduce 메서드를 많이 사용함!!(그 외에는 number나 string으로 변환할 때 사용함)

const arr = [
  "Host:localhost",
  "Connection:keep-alive",
  "Content-Type:application/json",
];

let initialValue = {};
const obj = arr.reduce((accumulator, currentValue, index, array) => {
  const [key, val] = currentValue.split(":"); // Host:localhost --> [Host, localhost]
  accumulator[key] = val;
  return accumulator;
}, initialValue);
console.log(obj);


/*
for (let i = 0; i < arr.length; i++) {
  console.log(arr[i]);
}
*/

/*
// 첫번째 인자값: arr 배열에 있는 요소
// 두번째 인자값: arr 배열에 있는 인덱스
// 세번째 인자값: arr 배열 자체
arr.forEach((value, index, array) => {
  console.log(value);
});
*/

 

 

 

1-4) HTML 불러오기

server.js

const net = require("net");
const resFn = require("./lib/res");
const reqFn = require("./lib/req");
const PORT = process.env.SERVER_PORT || 3000;
const HOST = process.env.SERVER_HOST || "127.0.0.1";

const server = net.createServer((client) => {
  client.setEncoding("utf8");

  client.on("data", (chunk) => {
    // "chunk"는 client에서 보낸 메시지를 담고 있음!!
    // console.log(chunk);
    const res = resFn(client); // 여기서 res의 데이터 타입은 "객체(Object)"이며, 해당 객체의 속성은 send, sendFile로 2개이다!!
    const req = reqFn(chunk); // chunk는 request message로 데이터 타입은 "string"이다!!
    // @TODO : request message 파싱
    // @TODO : response message 만들기

    if (req.method === "GET" && req.path === "/") {
      res.send("<h1>응답~~~</h1>");
    } else if (req.method === "GET" && req.path === "/list") {
      res.sendFile("list.html");
      // sendFile : file을 주는 것이 아니라 file을 읽고 response message를 만들어 해당 response message를 주는 역할을 한다!!
    } else if (req.method === "GET" && req.path === "/write") {
      res.sendFile("write.html");
    } else if (req.method === "GET" && req.path === "/update") {
      res.sendFile("update.html");
    } else if (req.method === "GET" && req.path === "/view") {
      res.sendFile("view.html");
    }
  });
});

server.on("connection", () => {
  console.log("connected to client");
});

server.listen(PORT, HOST, () => {
  console.log("server start");
});

 

res.js

const fileRead = require("./template");
const message = (content) => {
  const body = Buffer.from(content);

  return `HTTP/1.1 200 OK
Connection: Close
Content-Type: text/html; charset=UTF-8
Content-Length: ${body.length}

${body.toString()}`;
};

// 요청을 간단하게 만들어주는 함수
module.exports = (client) => {
  return {
    send: (body) => {
      const response = message(body);
      client.write(response);
    },
    sendFile: (filename) => {
      // filename : 'list.html'
      const body = fileRead(filename); // 결과값 : string(html 내용들)
      const response = message(body);
      client.write(response);
    },
  };
};

 

template.js

const fs = require("fs");
const path = require("path");
const DEFAULT_DIR = "../views";

module.exports = (filename) => {
  const target = path.join(__dirname, DEFAULT_DIR, filename);
  // @TODO : filename 매개변수 안에 있는 파일을 가져와서 HTML을 return함!!
  // @TODO : 파일을 읽을 수 있는가?
  const readline = fs.readFileSync(target, "utf8");
  return readline;
};

 

list.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <h1>로고입니다.</h1>
    <h2>게시판 리스트입니다.</h2>
    <table>
      <tr>
        <th>번호</th>
        <th>제목</th>
        <th>작성자</th>
        <th>작성일</th>
        <th>조회수</th>
      </tr>
    </table>
  </body>
</html>

 

write.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    Write 페이지 입니다.
  </body>
</html>

 

update.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    Update 페이지 입니다.
  </body>
</html>

 

view.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    View 페이지 입니다.
  </body>
</html>

 

 

URI, URL의 차이
- URI가 URL의 상위 개념이다!

  • URI : file이 아닌 것
  • URL : file에 해당하는 것(".do"를 제외한 모든 확장자의 파일로 파일경로가 마무리되는 것)

 

동적 웹 페이지 / 정적 웹 페이지

  • 정적 웹 페이지 : 하나의 html로 하나의 페이지만 보여주면 정적 웹 페이지(html의 내용을 바꾸지 않는 한 항상 동일한 페이지를 보여주는 것)
  • 동적 웹 페이지 : 하나의 html로 여러 데이터를 표현할 수 있으면 동적 웹 페이지