Node.js | 여러 페이지의 라우팅 및 폼 POST 전송 | 여러 페이지의 라우팅 개념

Node.js으로 간단한 페이지를 표시할 수 있게 됐다. 그런데, 한 페이지만 표시가 된다면, 실제로 사용할 수가 없다. 일반 Web에서는 더 다양한 요청에 처리를 해야 한다.

여러 페이지 작성

우선은 “복수의 페이지"부터 생각해 보자. 보통 Web이라는 것은 여러 페이지가 있다. Node.js에서 여러 페이지를 표시하려면 어떻게해야 하나?

여기에서는 전회에 사용한 EJS라는 템플릿 엔진을 사용하여 생각을 해보록 한다(사용하지 않아도 개념은 같지만 …). 기본적인 개념은 매우 간단하다. 여러 페이지를 이용하려면, 먼저 그 페이지를 미리 로드해 두고, 요청에 따라 어떤 페이지를 렌더링하여 표시할지 여부를 결정하면 된다.

그럼 “어느 페이지를 표시할지"를 클라이언트에서 어떻게 전달할 수 있는가? 보통의 Web 사이트에서는 URL에 의해 그것은 전달할 수 있다. http://xxx/index라면 index 페이지를 http://xxx/helo라면 helo 페이지를…… 이와 같은 식이다.

보통의 Web 서버에서는 자동으로 해당 HTML 페이지를 로드하여 반환하지만, Node.js의 경우 프로그램 중에 이러한 처리를 해야 한다. 보내 온 URL에서 도메인 이후의 부분을 얻어서 그 값에 의해 표시하는 페이지를 바꾸는 등의 작업을 생각할 수 있다.

그럼 실제로 해보자. 먼저, 아래 준비로 표시에 사용하는 EJS 템플릿 파일을 준비해 둔다. 여기에는 페이지 전체의 레이아웃이 된다. “template.ejs”(이전까지 hello.ejs라는 파일명으로 사용하고 있었던 파일이다)과 실제 표시되는 컨텐츠가 되는 ‘content1.ejs”, “content2.ejs"까지 총 3개의 템플릿 파일을 준비하록 한다.

template.ejs

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta http-equiv="content-type"
        content="text/html; charset=UTF-8">
    <title><%=title %></title>
    <style>
    body { font-size:12pt; color:#006666; }
    h1 { font-size:18pt; background-color:#AAFFFF; }
    pre { background-color:#EEEEEE; }
    </style>
</head>
 
<body>
    <header>
        <h1 id="h1"><%=title %></h1>
    </header>
    <div role="main">
        <p><%-content %></p>
    </div>
</body>
 
</html>

content1.ejs

<h2>예제로 작성한 컨텐츠입니다.</h2>
<p><%= message %></p>
<hr>
<p><a href="/other">other pageへ</a></p>

content2.ejs

<p>다른 페이지의 컨텐츠이다.</p>
<p><%= message %></p>
<p> </p>
<p><a href="/">돌아가기</a></p>

이 파일들을 사용하는 두 페이지를 표시하는 스크립트를 작성한다.

url개체를 사용하여 URL 처리

그럼, 준비한 템플릿을 사용하는 스크립트를 작성한다. 이것은 앞에 스크립트를 보고나서 설명을 하는 편이 빠를 것이다.

아래에 Node.js 스크립트 예제는 아래와 같다.

var http = require('http');
var fs = require('fs');
var ejs = require('ejs');
var url = require('url');
 
var template = fs.readFileSync('./template.ejs', 'utf8');
var content1 = fs.readFileSync('./content1.ejs', 'utf8');
var content2 = fs.readFileSync('./content2.ejs', 'utf8');
 
var routes = {
    "/":{
        "title":"Main Page",
        "message":"이것을 예제 페이지입니다.",
        "content":content1},
    "/index":{
        "title":"Main Page",
        "message":"이것을 예제 페이지입니다.",
        "content":content1},
    "/other":{
        "title":"Other Page",
        "message":"다른 페이지 표시하고 있다.",
        "content":content2}
};
 
var server = http.createServer();
server.on('request', doRequest);
server.listen(1234);
console.log('Server running!');
 
// 요청 처리
function doRequest(request, response) {
    var url_parts = url.parse(request.url);
    // route check
    if (routes[url_parts.pathname] == null){
        console.log("NOT FOUND PAGE:" + request.url);
        response.writeHead(200, { 'Content-Type': 'text/html' });
        response.end("<html><body><h1>NOT FOUND PAGE:" + 
            request.url + "</h1></body></html>");
        return;
    }
    // page render 
    var content = ejs.render( template,
        {
            title: routes[url_parts.pathname].title,
            content: ejs.render(
                routes[url_parts.pathname].content,
                {
                    message: routes[url_parts.pathname].message
                }
            )
        }
    );
    response.writeHead(200, {'Content-Type': 'text/html'});
    response.write(content);
    response.end();
}

이를 실행하여 http://127.0.0.1:1234/로 접근해 본다. content1.ejs의 내용이 표시된다. 이 페이지에있는 링크를 클릭하면, http://127.0.0.1:1234/other로 이동하고 content2.ejs의 내용을 표시한다.

그럼 동작이 확인되었다면, 스크립트의 내용을 보면서 처리 방법을 설명하겠다.

url의 로드

var url = require('url');

URL을 처리하기 위해서는 “url"라는 개체를 로드한다. 이 url 객체는 URL 문자열을 파싱하고, 거기에서 필요한 것을 얻어오는 기능을 제공한다.

URL의 파싱

var url_parts = url.parse (request.url);

요청이 액세스해 온 URL을 파싱 처리한다. 요청된 URL은 request 이벤트 핸들러의 인자로 전달된 request 객체의 ‘url’이라는 속성(property)에서 얻을 수 있다.

url 객체의 “parse"는 URL의 문자열을 요소마다 분할하여 오브젝트화 하여 반환한다. 이것으로 변수 url_parts에 URL 요소가 저장된다. 각각의 요소는 생성된 객체의 속성으로 저장되어 언제든지 사용할 수 있다.

각 페이지의 데이터를 준비한다

이번 스크립트에서는 각 페이지의 데이터를 routes라는 변수에 정리하고 있다. 이것은 액세스하는 곳의 경로를 키로 준비하고, 그 경로에 표시되는 페이지의 정보를 연관 배열로 정리한 것을 값으로 설정되어 있다. 예를 들어, 루트인 “/“의 값을 보면,

"/":{
    "title":"Main Page",
    "message":"이것을 예제 페이지입니다.",
    "content":content1 }

이런 식으로 되어있는 것을 알 수 있을 것이다. 연관 배열에는 title, message, content라는 키가 준비되어 있으며, 각각 “제목 텍스트”, “페이지에 표시하는 메시지 텍스트”, “표시하는 페이지의 내용(템플릿 데이터)“를 값으로 저장되어 있다. 이 변수 routes에서 액세스하는 주소의 경로마다 필요한 정보를 얻는 처리 하는 것이다.

경로를 얻을 수 없는 경우의 처리

if (routes[url_parts.pathname] == null){...}

변수 url_parts에 URL의 각 요소가 오브젝트로 저장되어 있는데, 이 중에 “경로"값은 “pathname"라는 속성으로 저장되어 있다.

변수 routes에는 기존에 언급한 바와 같이 각 경로마다 필요한 정보가, 경로를 키로 한 연관 배열에 정리되어 있다. 이는 routes[url_parts.pathname] 값을 꺼내면, 현재 요청이 액세스하고 있는 경로의 정보를 얻을 수 있게 된다. 만약이 값이 null이라면, 변수 routes는 정보가 없다는 것은 “그 경로에 액세스할 수 없다’라는 것을 의미한다.

그래서 null의 경우에는 에러 메시지 등을 표시해야 한다. 이것으로 준비되지 않은 주소로 대응을 할 수 있다.

액세스한 경로의 페이지를 렌더링하기

var content = ejs.render( template,
    {
        title: routes[url_parts.pathname].title,
        content: ejs.render(
            routes[url_parts.pathname].content,
            {
                message: routes[url_parts.pathname].message
            }
        )
    }
);

이후에는 변수 routes에서 필요한 값을 얻어 렌더링을 하면 된다. 예를 들어, title에 설정하는 값은

title: routes[url_parts.pathname].title

이렇게 준비하면 되고, 컨텐츠의 렌더링을 content 준비하려면

content: ejs.render( 
    routes[url_parts.pathname].content, ...)

이와 같이하면 된다. 여기에 표시할 내용의 템플릿은 routes[url_parts.pathname].content에서 얻을 수 있기에, 이를 render 할 뿐이다.

그 후로는 writeHead, write, end 세트로 실행하여 페이지 출력이 완료된다. 템플릿 렌더링 처리가 있기 에 조금은 귀찮게 보이지만, 기본적으로 “페이지에서 사용하는 값은 routes[url_parts.pathname]에 정리되고 있다"라는 것을 알고 있으면 어렵지 않다.




최종 수정 : 2018-07-16