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로 여러 데이터를 표현할 수 있으면 동적 웹 페이지
'Node.js' 카테고리의 다른 글
Node.js(8) - express (0) | 2022.12.14 |
---|---|
Node.js(7) - HTTP, 동적 웹 페이지 구현 (0) | 2022.12.13 |
Node.js(5) - TCP, HTTP(Request Message / Response Message), express (0) | 2022.12.09 |
Node.js(4) - TCP, 프로토콜, request message / response message (0) | 2022.12.08 |
Node.js(3) - Network, TCP Server/Client 구축 (0) | 2022.12.07 |