실습 코드 샘플 다운로드
https://github.com/JonathanWexler/get-programming-with-nodejs.git
GitHub - JonathanWexler/get-programming-with-nodejs: Code samples for Get Programming with Node.js (See verhagen's VM setup for
Code samples for Get Programming with Node.js (See verhagen's VM setup for exercises in this book: https://github.com/verhagen/get-programming-with-nodejs) - JonathanWexler/get-programming-with...
github.com
명령어: git clone
오늘은 lesson6/7을 진행
01. 라우트와 외부 파일
지난 시간: 라우트 맵을 이용한 url에 따른 html 코드 매칭.
자바스크립트 코드 상에서 html 코드 헤더인 <h1> 태그를 사용해 동적으로 html 화면을 만들수는 있다.
하지만 동적 페이지를 만들때는 당연히 코드화를 해서 경우에 따라 다른 페이지를 만들어내는 동적 페이지 형식이 더 좋다.
만약 정적 페이지만 필요하다면, html 코드 자체를 자바스크립트 안에다 전부 넣어서 복잡하게 만들 필요가 있는가?
코드 가독성만 떨어지게 된다.
이번 시간에는 fs 모듈을 활용해 정적 파일을 어떻게 클라이언트에게 제공하는지를 알아보자.
1. fs 모듈을 이용한 정적 파일 제공
정적 페이지를 위한 html 파일을 미리 만들어놓으면 된다.
응답을 할 때 html 파일로 응답하면 된다.
이런 정적 페이지를 위한 html 파일을 따로 보관할 필요성이 있다.
일종의 모음 = views.
- main.js : 진입점.
- package.json: npm init로 생성.
- views: 정적 페이지를 위한 html 파일 모음.
1) 간단한 실습.
(1) 프로젝트 디렉토리를 만든다.
(2) main.js 파일을 생성한다.
(3) views 디렉토리를 만든다.
(4) 내부에 index.html 파일을 생성한다.
내부 내용은 크게 어렵지 않다.
이 수업에서는 html/css는 커버하지 않고, 자바스크립트 위주로 사용한다.
다음과 같은 html 파일을 만들었다면, 이를 참조할 수 있어야 한다.
참조하기 위해서 사용하는 것이 fs 모듈.
자바스크립트 내부에 html 코드가 있는 것이 아닌,
외부에서 index.html 파일을 읽어서 클라이언트 응답에 사용하는 것이 목표.
2) fs 모듈을 사용한 서버 응답
(1) fs 모듈 요청
const fs 변수 선언, require("fs") - 해당 모듈을 불러온다.
코어 모듈이기 때문에 npm install 작업은 불필요하다.
- fs 모듈은 어떤 역할을 하는가?
자바스크립트는 원래 시스템을 컨트롤 할 수 없다.
(runtime, OS.) 자바스크립트 애플리케이션은 이 위에 작성된다.
fs 모듈은 runtime 안에 구현되어 있다.
runtime에서 자바스크립트를 해석하기 때문에, OS가 read/open/write 시스템 콜을 호출할 수 있다.
운영체제의 기능을 활용하기 위해서 시스템 콜을 대신 호출해준다.
runtime에 필요한 fs 모듈의 기능을 요청하기 위해 - require 해준다.
fs 모듈 요청: 애플리케이션 대신 파일시스템과 상호작용한다.
(2) HTML 파일에 매핑되는 라우트 설정
라우트 맵에 하나를 추가.
"/"이 요청될 경우 : 리턴되는 것은 다음의 문자열이다.
원래는) HTML 코드가 들어있었다. (=문자열이긴 하지만)
이 경우에는 파일 경로명이다.
라우트 맵을 통해 URL을 제공하면, 그에 응답하기 위한 파일의 경로명이 리턴된다.
하나뿐만 아니라 여러개의 정적 페이지가 존재한다면, 여러개가 적힌다.
다음은 상대 경로명이다. 현재 디렉토리를 기준으로 해서 views/index.html이다.
프로젝트 안에서 파일을 접근할때는 상대 경로를 사용한다.
절대 경로를 사용한다면, 이 애플리케이션을 어느 경로에 설치했냐에 따라 절대경로가 달라진다.
상대경로는 실행파일을 기준으로 실행되기 때문에, 주로 상대 경로를 사용한다.
절대 경로는 다른 컴퓨터에서 작동하지 않을 수 있다.
(3) 매핑된 파일의 콘텐츠를 읽어 응답 메세지에 포함
서버를 생성하고 있다.
createServer은 매개변수로 콜백 함수를 받고 있다.
화살표 함수로 .writeHead 사용.
.listen(port) 전까지 콜백 함수의 끝이다.
req, res가 주어지면 그것을 매개변수로 사용해 중괄호 안에 있는 것을 실행한다.
.writeHead 함수는 html 파일이라는 것을 명시하는 헤더 함수.
다음이 중요하다.
가장 먼저 routeMap[req.url]을 if문으로 확인한다.
=> 호스트 네임 + 루트'/' + 다음에 들어오는 contact이 있다면 '다음에 있는 것'이 출력된다.
이 부분을 조회해서, 존재할 경우 다음의 빨간색 박스가 실행된다.
없는 경우, else에서 직접 html 코드를 호출한다.
라우트 맵에 요청된 url이 존재하는 경우.
fs.readFile 함수 활용. 두개의 인자를 받는다 - routeMap[req.url]: 읽으려는 파일의 경로, 두번째는 콜백 함수를 받는다.
첫번째 매개변수인 파일 경로로부터 파일을 읽어서, 두번째의 콜백 함수에 넘겨준다.
error: 에러가 나면 에러 변수의 값을 할당. data: 읽은 내용이 데이터에 들어간다.
콜백 함수는 파일을 읽은 다음, 실행될 콜백 함수이다.
데이터에는 이미 파일의 내용이 들어가게 된다.
res.wirte(data); 작성한 응답의 본문 부분이 들어가게 된다.
res.end()를 바로 사용해도 된다.
어쨌든 파일 내용을 옮기고, 다 했다고 클라이언트에게 알린다.
다음은 성공적으로 해낸 경우 - 응답으로 정상적으로 실행했다고 보낸다.
(4) 읽기에 실패한 경우? 첫번째 매개변수 error에 에러
fs.readFile이 무조건 읽을 수 있다는 보장이 없다.
route[req.url]에 해당 파일이 없을 수 있다 -> 에러 부분에 에러값이 전달된다.
기본적으로 콜백 함수에 error란 데이터가 전달된다 - 첫번째 매개변수는 무조건 error, 두번째는 파일을 읽은 데이터가 저장되는 매개변수.
(5) fs 모듈을 사용한 서버 응답
기본은 정상적으로 작동.
contact 등은 찾을 수 없다.
그동안은 라우트 맵에 어떤 파일을 읽어올지를 입력했다. ex. '/' = '파일경로'.
적을때는 문제가 되지 않지만, 많아지면 문제가 된다.
=> 동적 읽기를 사용한다.
3) 동적 읽기를 통한 정적 파일 제공
(1) URL을 파일 경로로 변환하기 위한 함수 생성
url이 주어졌을 때, url을 어떤 파일 경로로 변환하기 위한 함수를 제공하면 된다.
<-> 원래는 라우트 맵을 사용해 하나하나 경로를 입력했다.
ex. "/" : "views/index.html"
이런 식으로 입력할 필요가 없다.
url이 주어진다고 가정했을때, 그것을 매개로 해서 - 'views(상대 경로)${url}.html'
=> index가 주어진 경우: views/index.html로 변환된다.
일일히 입력할 필요 없이, 원하는 html 파일을 찾도록 할 수 있다.
(2) 파일 경로 문자열 추출
viewUrl = 위에서 선언한 함수 getViewUrl(req.url);
변환되어 viewUrl에 저장된다. 즉, =파일 경로.
fs 모듈에서 read파일 등에 활용할 수 있는 파일 경로를 뽑아낸 것이다.
이 파일 경로를 활용하여 읽어들이면 된다.
(3) 요청 URL을 파일 읽기 경로로 활용
viewUrl은 경로, 다음은 콜백 함수. 매개변수는 error, data.
(4) 파일 읽기 실패 시 404 에러 코드 처리
error 체크 추가.
만약에 readFile을 하다가 실패해서 에러부분이 세팅되어 있다면 (리드 이후에 실행되는 콜백함수) wirteHead(NOT_FOUND = 404)를 명시한다.
이 경우, 응답에 적어준다. 이때 end()를 호출하지 않고 마지막에 호출된다.
(5) 파일 읽기 성공 시 파일 내용으로 응답
성공한다면 - 잘 읽어왔기 때문에 Head에는 OK.
헤더 부분에 html타입이라고 명시를 꼭 해줘야 한다.
write(data)를 응답에 적어준다.
에러가 발생하던, 하지 않던, 최종적으로 end();를 호출해서 종료.
listen(port);하면 해당 서버가 해당 포트를 기다리게 된다.
(6) 테스트
의도한 결과.
"/"가 들어간 경우: views./.html -> /.html이라는 파일은 존재하지 않음 => 404
4) view를 보여주기 위한 라우팅 로직
다시 한 번 정리.
view라는 것은 클라이언트 측에 보여지는 화면.
라우팅이 어떻게 동작하는가?
1) 사용자(웹 브라우저)가 애플리케이션에 요청을 보낸다.
2) 웹서버가 요청을 받는다.
3) 여러개의 라우트가 존재하는데, 요청에 따라 어떤 것을 요청받았고 무엇으로 응답하는지 결정한다.
<-> 경우에 따라: 페이지를 동적으로 만들기도 한다.
4) 동적으로 만들기 위해 데이터 처리 필요, 어떤 DB의 정보가 필요하다던지, 이것은 애플리케이션 서버에서 처리한다.
웹서버는 사용자에게 보여줄 부분에 집중한다.
애플리케이션 서버는 내부적인 로직에 집중한다.
5) 애플리케이션 서버에서 필요한 것을 웹서버에 전달하면, 최종적으로 웹서버는 응답한 내용을 조립해서 어떠한 형태 - HTML, text, JSON 등으로 응답해준다.
다음과 같이 라우팅이 진행된다.
정적 페이지뿐만 아니라 기타에 해당하는 정적 파일들도 존재한다. 그것을 에셋으로 표현한다.
2. 에셋asset 제공
서버에서 필요하기보다, 클라이언트 = 웹 브라우저에서 우리 눈에 보여주기 위해 필요한 파일들을 모아놓은 것.
이미지 파일, 스타일시트, 자바스크립트 관련 파일들이 전부 에셋이다.
공통점: 클라이언트에서 필요한 파일.
우리가 물론 자바스크립트로 node.js를 작성하고 있긴 하지만, 다르다. 이것은 서버측, 저것은 클라이언트측.
HTML 파일을 웹브라우저에 띄운다고 할 때, 스타일 부분을 설정할 수 있는 것이 스타일시트.
이미지들을 띄우기 위한 것이 이미지 파일.
serve_html 안에 main.js, package.json
views -> 정적 페이지들.
에셋들은 public이라는 디렉토리를 만들어, 내부에 종류에 따라 별도의 디렉토리를 만들어 구조화해 관리한다.
1) 각 파일별 특정 라우트를 가지는 웹 서버 구현
(1) 에러 핸들링 함수 생성
sendErrorResponse 함수는 res라는 매개변수를 가지고,
헤더에 NOT_FOUND를 넣고, html을 리턴할 것이라고 공지한다.
write("에러에 대한 응답")
=> 에러에 대한 응답을 만들어서 재활용한다.
(2) indexof(): 찾고자 하는 문자열의 index 리턴
각 파일의 특성에 따라(url의 확장자에 따라) 다른 방식으로 처리할 수 있다.
어떤 url이 들어왔냐에 따라.
.html 파일이 요청되었다면
-> views/에서 읽어오고
.js 파일이 요청되었다면
-> public/js/에서 읽어오고
. . .
url = req.url; 은문자열이다.
url.indexof 해주면 처음 등장하는 인덱스를 리턴해준다.
예를 들어, url이 "/index.html" 이라면 이 문자열에서 .html을 찾아서, 그 부분을 리턴한다.
그 결과값은 -1이 아니게 된다.
-1이 나왔다면 url에서 .html이 나오지 않았다는 것이다.
=> html이라는 확장자가 아니라는 것이다.
각각 해당하는 if문마다 그 확장자인지 확인하는 것이다.
모두에게 해당하지 않는다면 - sendErrorMessage로 에러 응답을 한다.
(3) 요청 콘텐츠 유형의 지정
url 유형 확인뿐만 아니라,
헤더값이 전부 OK이지만, content-Type이 전부 다를 수 있다.
이것들을 전부 다르게 처리해준다.
(3) 요청 파일을 읽어 오기 위한 경로 지정
-> 위와 이어진다.
ReadFile에서 '/views${url}'로 묶고,
'./public/js${url}로 묶고,
. . .
이런 식으로 각 확장자, url에 따라 경로를 다르게 지정한다.
(4) 코드 반복을 줄이기 위한 파일 읽기 함수 구현
실제로 파일을 읽어올때는 fs.readFile을 실제로 쓰지 않고, 새로운 함수를 만들어서 사용한다.
중복을 줄이기 위해서.
이 안에서 하는 것은 1) 파일이 존재하는지 체크
2) 파일이 존재한다면 - 파일을 읽고있고
3) 에러가 존재하면 콘솔창에 에러를 남겨주고, 실패했다며 리턴한다.
4) 에러가 없다면 읽은 내용을 적어주고, 그 내용을 클라이언트에게 응답을 전해준다.
5) 파일이 존재하지 않으면: 에러를 보낸다.
-> 호출마다 반복하지 않기 위해 함수를 만들었다.
2) 라우트를 다른 파일로 바꿔 연결하기
- 라우팅 고려요소
(1) HTTP 메소드
GET/HOST: 동일한 URL에 따라 GET일수도, HOST일수도 있다.
(2) URL
- 모든 라우트를 if-else문으로 처리한다면?
코드 가독성이 떨어지고, 라우트 변경/삭제의 경우 번거롭다.
-> 어떻게 라우트를 분리하는가?
3) 라우트 부분의 분리
(1) GET, POST 요청에 매핑된 라우트 저장을 위한 객체
라우트의 경우, 일반적으로 http 메소드를 기준으로 구분하는게 기본.
routes라는 객체 생성.
GET이라는 키워드-객체 보유.
객체 안에는 "/info" : 콜백 함수와 연결되어 있다.
info가 함수의 이름이 된다.
POST는 아무것도 없는 빈 객체를 의미.
GET = 하나의 객체 의미.
모듈화를 할 예정. 별도의 route.js 파일을 만들어서 작업.
Content-Type을 html으로 기본적으로 설정.
모듈을 따로 분리할때, exports를 해야 다른 자바 스크립트 파일에서 참조가 가능.
handle, get, post - 전부 화살표 함수.
뒤에서 설명.
(2) 라우트에 따른 콜백 함수를 처리하기 위한 함수 생성
routes 모듈을 다른 쪽에서 require, import를 통해 사용.
exports.handel이라는 함수에 접근 가능.
handle = 화살표 함수. req, res는 http 서버가 호출될때마다 사용되는 콜백함수로 사용. 매번 요청이 들어올 때마다 실행. 매개변수로 들어오게 되면, try-catch로 묶여있다.
routes 객체에서 -> req.method = 메소드 의미, req.url = url 의미
1) 메소드 부분에 GET이 들어왔다면, GET에서 요청 url을 검색한다.
메소드와 url을 가지고 원하는 함수를 찾을 수 있다.
GET와 /info가 있다고 가정하자. routes에서 GET -> /info에 있는 것을 실행한다.
함수니까 괄호를 넣어주면 실행 = routes[req.method][req.url](req, res): 매개변수를 안에 넣어주고 함수를 호출.
함수가 경우에 따라 호출되도록 간단하게 제작.
routes가 존재하지 않는다면?
else로 빠져서 NOT_FOUND + res.end()로 설정.
함수를 실행시키다가 잘못 실행시켰다면, try-catch문이기 때문에 catch해서 error을 출력하게 했다.
위의 둘은 다르다. 실행되다가 문제가 생긴 경우, 라우터에 존재하지 않은 경우.
(3) routes에 등록하기 위한 get, post 함수 생성
exports.get = (url, action)
=> routes["GET"][url] = action;
GET의 url에 action 값을 할당해주고 있다.
이것은 어떤 url에 대해서 접근했을때, 존재하지 않을 수 있다. 그 경우 값을 넣어주면, 객체의 값을 삽입이 가능하다.
없는 것에 대해서 할당하는 경우 - 새로 추가된다.
/contact
() => {}; 새로운 것이 등록된다.
GET 메소드 관련한 것을 등록하기 위해서는 exports.get을 호출한다.
POST 메소드 관련한 것을 등록하기 위해서는 exports.post를 호출한다.
get, post는 라우터에 등록하기 위해서 사용하는 함수이다.
(4) 전체적인 그림
get, handle, post를 별도의 router.js파일로 분리했다.
router.js 안에 routes 객체가 있다.
이것은 exports를 하지 않았기 때문에, router.js만 접근 가능하고 외부는 접근 불가능하다.
exports한 함수를 통해서만 routes에 insert/실행이 가능하다.
exports를 하게 되면: 모듈을 require을 통해 import를 한 곳에서만, Exports에 접근할 수 있게 된다.
4) 라우트 파일 연결하기
(1) 모듈 불러오기
실제로 router.js 파일을 main.js에 가져와서 실행시킬 필요 있음.
그렇게 하기 위해서, router = require("./router.js") 를 통해 router.js을 불러온다.
다음부터는 router을 통해서 router.handle, get, post를 호출할 수 있게 된다.
(2) 코드 반복을 줄이기 위해 함수 생성
files(=경로 요청),
readFile('./${file}', 콜백 함수 - errors, data.
에러 처리.
res.end(data); 데이터를 출력.
함수는 자동으로 응답까지 해주는 함수.
읽기 + 응답까지.
(3) get와 post로 라우트 등록
우리가 실제로 등록을 해야한다.
아까의 get, post의 경우, 라우터를 등록할때 사용하는 함수.
실제로 필요한 것들을 함수를 사용해 등록.
routes에 요청이 들어왔고, get 메소드라면, 안쪽에 있는 콜백 함수가 등록된다. 안쪽에 있는 애들이 실행.
OK, ContentType.
end("INDEX");
(4) router.js의 handle 함수를 콜백함수로 등록
다음은 요청이 올 때마다 호출이 되는 콜백 함수.
router.handle로 등록.
get, post를 나누고, url에 따라 쉽게 등록할 수 있도록 분리해둔 상태.
지금까지 라우터를 내부파일로 분리하는 방법.
02. 캡스톤 프로젝트: 첫번째 웹 애플리케이션
-> 앞으로 수업을 진행하면서 업그레이드 할 예정
팀프로젝트에도 활용 가능.
가장 먼저 정적 페이지 제작.
1. 지역 요리 학교를 위한 웹사이트 구축
1) 애플리케이션의 초기화
시작 포인트로 main.js
설명에 "A site for booking classes for cooking." 추가
2) 애플리케이션 디렉토리 구조의 이해
- public VS views
= 에셋들을 저장한 곳 VS 정적 페이지를 모아놓은 곳
4가지 자바스크립트 파일 존재.
main.js = 진입 포인트.
router.js, contentType.js, utils.js = 모듈.
public - css, images, js.
views - 정적 html.
3) router.js 및 main.js 파일의 생성
(1) main.js에 필요한 모듈 임포트
참조할 router, contentTypes, utils 모듈 임포트
(2) contentTypes 모듈 생성
html, text, js 등등의 Content-Type 결정.
html이 들어오면, "Content-Type" : "text/html"을 돌려준다.
헤더 작성시 이용.
(3) utils 모듈 생성
파일을 읽는 부분을 아예 여기로 분리했다고 생각하면 된다.
이 경우, 읽는 것만 있기 때문에: getFile만 존재한다.
module.exports로 getFile을 exports해줬다.
getFiles는 file, res를 매개변수로 받아, 인자로 들어온 ${file}을 사용해서 data를 읽어오고, error가 발생한 경우 공지하며, 이때 contentTypes.html을 활용하고, 메세지를 출력한다.
=> 이때 contentTypes.html을 활용하기 위해 상단에서 require했다.
모듈도 다른 모듈을 필요로 한다면, require해야 한다.
성공적으로 읽은 경우, 읽어온 데이터를 응답으로 보내준다.
(4) router 모듈 생성
(a) 라우트 함수를 위한 routes 객체 생성
routes 객체를 기본적으로 만들고 시작.
등록할 것이기 때문에 안쪽을 비워놓는다.
여기에다가 추가하면 된다 - get, post를 활용해서.
(b) 요청을 처리하기 위한 handle 함수 생성
method, url을 체크해서, 함수를 실행한다.
만약에 없다면 - 에러가 난다. 에러가 catch가 되어 들어온다. OK, contentType, gerFile으로 리턴해주는 것은 error.html이다.
에러 정적 페이지를 보여주도록 세팅한다.
성공적으로 처리할 수 있는 요청이 들어오면 위가 되고, 아니라면 아래가 된다.
(c) routes에 등록하기 위한 get/post 함수 생성
아까랑 동일.
=> router.js, main.js, 이외 여러가지 모듈들을 만들었다.
4) 뷰 페이지 생성
여러가지 html 파일을 만든다.
index.html
courses.html
contact.html
thanks.html
-> contact의 경우
form이라는 태그가 있다. 활용하면 routes에 대해 어떤 method를 사용해서 아래 내용을 보낼 것인지를 정할 수 있다.
"/"에 post에 보내고, 타입은 이메일.
들어갈 이메일 내용, 웹서버 쪽에서 읽어올 수 있다.
버튼은 submit. 클릭하면 호스트에 "/"로 routes에 요청이 보내진다.
5) 에셋 추가
- public/css
-> bootstrap.css: 기본 스타일시트 설정 파일
-> confetti_cusine.css: 사이트에서 사용될 요소들의 색상, 차원, 위치 변경 정보
- public/js
클라이언트에서 사용할 자바스크립트 파일
HTML에서 <script> 태그를 통해 읽어들인다.
- public/images
클라이언트에서 사용할 이미지 파일
HTML에서 <img> 태그를 통해 읽어들인다.
6) 라우트 등록
(1) 다음을 고려해서 라우트를 등록해야 한다
- 요청이 GET인지, POST인지
- 어떤 URL을 라우트할지
- 돌려줄 파일의 이름 = 경로를 알아야 읽어서 전달해준다
- HTTP의 상태 코드
- 돌려줄 파일 타입 (Content Type)
(2) main.js 파일에 웹 페이지를 위한 라우트 목록 추가
get - "/"가 요청되는 경우: OK, ContentTypes.html, getFile("views/index.html");
post - "/" : OK, ContentTypes.html, getFile("views/thanks.html");
(3) main.js에 에셋을 위한 라우트 목록 추가
"/grap.png"파일을 요청할 경우 - 변한을 해서 ContentTypes.png, 나아가 getFile("public/images/graph.png")에서 파일을 찾도록 한다.
css 파일들도 동일.
마지막에 router.handle을 등록해야 서버가 요청을 받을 때마다 - 알아서 판단해 router에 따라 콜백 함수를 호출한다.
눈에 보이는 웹사이트 제작.
정적인 페이지들을 띄워주고, 경우에 따라 필요한 페이지/에셋들을 가져와 보여주는 것까지 구현.
다음에는 express.js라는 웹 프레임워크 - Node.js의 확장판. 웹 개발을 할 때 자주 사용되는 것들을 묶은 라이브러리의 일종. 모아놓은 프레임워크.
제공하는 라이브러리를 활용하면 애플리케이션 구현이 간단하다.
express.js를 사용하면 훨씬 수월하다.
'강의 정리 > 서버시스템 구축 실습' 카테고리의 다른 글
서버시스템구축실습 (9) 데이터베이스 연결 (0) | 2024.05.14 |
---|---|
서버시스템구축실습 (8) Express.js를 통한 웹 개발 (1) (1) | 2024.05.04 |
서버시스템구축실습 (6) Node.js 기초 (1) (0) | 2024.04.25 |
서버시스템구축실습 (4) 자바스크립트 기초 (0) | 2024.04.02 |
서버시스템구축실습 (2) 웹서버와 데이터베이스 개요 (0) | 2024.03.19 |