6장에서 다룰 내용
- fs 모듈을 이용한 전체 HTML 파일의 저장
- 정적 에셋 저장
- 라우트 모듈 생성
6.1. fs 모듈을 이용한 정적 파일 제공
HTML 파일의 일부분을 사용해 페이지를 구성한다면 main.js 파일이 복잡해진다.
응답할 HTML 파일을 따로 작성하고, 동일한 프로젝트 디렉터리 내 views 폴더에 저장한다.
결과적으로 사용자에게 표시할 모든 내용은 views 폴더에 저장되고, 표시할 내용을 결정하는 모든 코드는 main.js 파일에 저장된다.
다음 단계를 따른다.
- 프로젝트 폴더를 만든다.
- 프로젝트 폴더 안에 main.js 파일을 만든다.
- views 폴더를 만든다.
- views 폴더 안에 index.html 파일을 만든다.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Home Page</title>
</head>
<body>
<h1>Welcome!</h1>
</body>
</html>
클라이언트는 파일시스템과 상호작용하는 fs 모듈의 도움으로 브라우저에서 이 페이지를 볼 수 있다.
먼저 fs.readFile()을 사용해보자.
const port = 3000,
http = require("http"),
httpStatus = require("http-status-codes"),
fs = require("fs");
// 라우트에 html 파일을 연결한다
const routeMap = {
"/" : "views/index.html"
};
const app = http.createServer((req, res) => {
res.writeHead(httpStatus.OK, {
"Content-Type" : "text/html"
});
// req.url을 fs.readFIle()로 읽어들여 사용한다
if (routeMap[req.url]) {
fs.readFile(routeMap[req.url], (error, data) => {
res.write(data);
res.end();
});
} else {
res.end("<h1>Sorry, not found.</h1>");
}
});
app.listen(port);
console.log(`The server has started and is listening on port number: ${port}`);
먼저 fs 모듈을 상수로 요청한다. 이 상수를 통해 관련 디렉토리에 있는 일부 파일들을 특정할 수 있다.
다음으로 파일과 라우트 쌍인 routeMap을 생성한다.
라우트와 매핑된 파일을 읽어들인다. fs. readFile 은 error 및 data라고 하는 두 개의 분리된 매개변수를 통해, 발생할 수 있는 잠재 에러와 데이터를 돌려준다.
마지막으로 클라이언트에 돌려줄 응답 본문으로 데이터를 사용한다.
// url 함수 생성
const getViewUrl = (url) => {
return `views${url}.html`;
};
const app = http.createServer((req, res) => {
// url 함수 적용
let viewUrl = getViewUrl(req.url);
fs.readFile(viewUrl, (error, data) => {
if (error) {
res.writeHead(httpStatus.NOT_FOUND);
res.write("<h1>FILE NOT FOUND</h1>");
} else {
res.writeHead(httpStatus.OK, {
"Content-type" : "text/html"
});
res.write(data);
}
res.end();
})
});
app.listen(port);
console.log(`The server has started and is listening on port number: ${port}`);
다음은 특정 요청의 URL에 대해서만 파일을 보여준다.
이렇게 구성된 라우트는 사용자가 요청하는 파일을 동적으로 찾을 수 있게 해준다.
최종적으로 http://localhost:3000/index 에 접속하면 서버는 views/index.html 을 보여준다.
- views를 보여주기 위한 라우팅 로직
- 사용자가 애플리케이션에 요청을 보낸다.
- 애플리케이션 내 웹 서버가 요청을 받고 응답을 하기 위해 정해진 로직을 수행한다.
- 서버에 설정된 라우트가 브라우저에서 보일 콘텐츠가 어떤 것인지 결정한다.
- 웹 서버는 응답을 보내기 전에 데이터 처리를 위해 여러분의 애플리케이션 서버와 통신한다.
- HTTP 응답이 클라이언트로 HTML, text, JSON 또는 다른 유효 데이터 형태로 전송된다.
6.2. 에셋 제공
- 에셋: 애플리케이션에서의 이미지, 스타일시트, 자바스크립트 파일을 뜻하며 클라이언트 측에서 뷰 페이지와 함께 동작한다. HTML 파일과 마찬가지로, 애플리케이션 내에서 에셋도 각자 고유 라우트가 필요하다.
프로젝트의 루트 디렉토리에 public 폴더를 만들고 모든 에셋을 이 폴더로 이동시킨다.
public 폴더 하위에 images, css, js 폴더를 각각 만들고 에셋들을 분류한다.
// 에러 출력
const sendErrorResponse = res => {
res.writeHead(httpStatus.NOT_FOUND, {
"Content-Type" : "text/html"
});
res.write("<h1>File not Found!</h1>");
res.end();
};
// readFile 커스텀 함수
const customReadFile = (file_path, res) => {
if (fs.existsSync(file_path)) {
fs.readFile(file_path, (error, data) => {
if (error) {
console.log(error);
sendErrorResponse(res);
return;
}
res.write(data);
res.end();
});
} else {
sendErrorResponse(res);
}
};
const app = http.createServer((req, res) => {
let url = req.url;
// .indexOf("") 함수로 문자열 내에 ""가 없다면 -1을 리턴한다
// 있다면 다음 if 문은 참이 된다
if (url.indexOf(".html") !== -1) {
res.writeHead(httpStatus.OK, {
"Content-Type" : "text/html"
});
customReadFile(`./views${url}`, res);
} else if (url.indexOf(".js") !== -1) {
res.writeHead(httpStatus.OK, {
"Content-Type" : "text/javascript"
});
customReadFile(`./public/js${url}`, res);
} else if (url.indexOf(".css") !== -1) {
res.writeHead(httpStatus.OK, {
"Content-Type" : "text/css"
});
customReadFile(`./public/css${url}`, res);
} else if (url.indexOf(".png") !== -1) {
res.writeHead(httpStatus.OK, {
"Content-Type" : "image/png"
});
customReadFile(`./public/images${url}`, res);
} else {
sendErrorResponse(res);
}
});
app.listen(port);
console.log(`The server has started and is listening on port number: ${port}`);
다음은 파일을 읽어들이는 로직을 함수로 구현하고, if 문으로 지정된 파일 요청 유형에 맞게 불러들이는 코드다.
특정 파일 형식을 확인하고 응답 헤더를 설정한 뒤 파일 경로 및 응답 개체를 메소드에 전달한다. 이러한 동적 라우트로 여러 파일 유형에 대응할 수 있다.
또한 존재하지 않는 파일에 대한 대응을 할 수 있게 됐다.
6.3. 라우트를 다른 파일로 바꿔 연결하기
라우트의 수정과 관리를 쉽게 하는 법을 알아보도록 하자.
- 사용된 HTTP 메소드로 분리하는 것이 더 편리하다.
- 라우트 부분을 router.js 파일로 분리한다.
- 라우트들을 저장, 사용하는 방법을 재구성한다.
const httpStatus = require("http-status-codes"),
htmlContentType = {
"Content-Type" : "text/html"
},
// 라우트 생성
routes = {
"GET" : {
"/info" : (req, res) => {
res.writeHead(httpStatus.OK, {
"Content-Type" : "text/plain"
});
res.end("Welcome to the Info Page!");
}
},
"POST" : {}
};
// .handle 함수 생성
exports.handle = (req, res) => {
try {
if (routes[req.method][req.url]) {
routes[req.method][req.url];
}
else {
res.writeHead(httpStatus.NOT_FOUND, htmlContentType);
res.end("<h1>No such file exists</h1>");
}
} catch (ex) {
console.log("error" + ex);
}
};
// get, post 함수 생성
exports.get = (url, action) => {
routes["GET"][url] = action;
};
exports.post = (url, action) => {
routes["POST"][url] = action;
};
POST, GET 요청에 매핑되는 라우트를 저장하기 위한 routes 객체를 정의했다.
라우트의 콜백 함수를 처리하기 위한 handle 함수를 정의한다. 이 함수는 routes[req.method]를 통해 요청 HTTP 메소드에 따라 routes 객체에 엑세스하며, 이후 콜백 함수를 [req.url]을 사용해 요청된 URL로 찾는다.
이때 라우트가 없다면 httpStaus.NOT_FOUND를 돌려준다. 또한 handle 함수에서 객체의 라우트와 일치하지 않는다면 try-catch를 사용해 오류를 로깅한다.
더불어 get, post 함수를 정의하고 이를 exports에 추가해 main.js에서 새 라우트를 등록할 수 있게 한다.
const port = 3000,
http = require("http"),
httpStatus = require("http-status-codes"),
fs = require("fs");
// 라우터 require
router = require("./router.js");
// 콘텐츠 타입 정의
const plainTextContentType = {
"Content-Type" : "text/plain"
},
htmlContentType = {
"Content-Type" : "text/html"
};
// fs.readFile 함수 정의
const customReadFile = (file, res) => {
fs.readFile(`./${file}`, (error, data) => {
if (error) {
console.log("Error reading the file...");
}
res.end(data);
});
};
// 라우터 함수 사용
router.get("/", (req, res) => {
res.writeHead(httpStatus.OK, plainTextContentType);
res.end("INDEX");
});
router.get("/index.html", (req, res) => {
res.writeHead(httpStatus.OK, htmlContentType);
customReadFile("views/index.html", res);
});
router.post("/", (req, res) => {
res.writeHead(httpStatus.OK, plainTextContentType);
res.end("POSTED");
});
// 서버 생성 - handle 함수 연결
http.createServer(router.handle).listen(port);
console.log(`The server has started and is listening on port number: ${port}`);
마지막으로 main.js에 router.js를 가져온다.
require("./router")을 사용해 작성한 라우터를 가져온다.
최종적으로 서버 생성을 통해 모든 요청들은 라우트 모듈의 처리 함수에 의해 처리되며, 처리 함수에는 콜백 함수가 뒤따른다.
6.4. 요약
1) views 폴더 내의 HTML 파일을 찾기 위한 fs 모듈 추가
2) 애플리케이션 에셋까지 범위 확대
3) 어떻게 메인 애플리케이션으로부터 라우트를 선택적으로 등록하고 사용자 정의 모듈에 라우트를 매핑하는지
'교재 정리 > Node.js' 카테고리의 다른 글
Lesson 5. 수신 데이터 다루기 (0) | 2024.05.22 |
---|---|
Lesson 4. Node.js에서 웹 서버 만들기 (0) | 2024.05.17 |
Lesson 03. Node.js 모듈 생성 (0) | 2024.05.16 |
Lesson 02. Node.js 애플리케이션 실행 (0) | 2024.05.16 |
Lesson 0. Node.js 설정과 Javascript 엔진 설치 (0) | 2024.05.14 |