Node.js | Node.js 스크립트 기본

Node.js는 “http” 객체라는 것에서 “http.Server"객체를 만들어 서버를 구축한다. 또한 요청과 응답을 다루는 “request”, “response"객체에서 송수신을 수행한다. 이러한 객체의 기본적인 사용법에 대해 설명한다.

기본 스크립트와 http 객체

그럼 앞전에 작성한 스크립트를 보면서 Node.js 스크립트에 대한 기본에 대해서 설명하겠다. (아래의 코드 참조)

var http = require('http');
 
var server = http.createServer();
server.on('request', doRequest);
server.listen(process.env.PORT, process.env.IP);
console.log('Server running!');
 
// 요청 처리
function doRequest(req, res) {
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.write('Hello World\n');
    res.end();
}

기본 스크립트와 http 객체

1. http 객체 로드

var http = require('http');

먼저 최초에 해야 하는 것은 필요한 라이브러리들의 로드이다. 여기서는 require(..) 함수를 사용한다. 인수에 가져올 객체 이름을 지정하여 해당 객체가 로드되어 반환된다. 이것을 변수에 대입하여 이용한다.

여기에서는 “http"라는 객체를 로드이고, 이것은 말 그대로 HTTP의 각종 기능을 요약한 것이다. 여기에서 서버 객체를 만든다.

2. Server 객체 생성

var server = http.createServer();

http 객체의 createServer() 함수를 호출하여 http.Server 객체를 만든다. 이는 Node.js의 “서버"가 되는 부분이다. 이 객체를 준비하고 필요한 설정을 하여 서버로 실행한다.

여기에서는 인수없이 호출하고 있지만, 다음과 같이 작성 할수 있다.

http.createServer(function(x){
    ...필요한 처리...
}).listen(x);

아마도 Node.js 사이트 및 기타 해설 사이트를 보면 대부분이 이런 작성법으로 되어 있을 것이다. createServer 인수로 서버에서 요청을 받았을 때의 처리하는 함수를 작성한다. 그리고, 이 후에 listen이라는 대기하기 위한 메소드도 계속 작성되고 있다.

이 작성에는 전혀 문제가 없지만, 모두 한 덩어리로 작성되어 있기 때문에, 익숙해지기 전에는 조금 이해하기 어려울 수 있다. 여기에서는 쉽게 “createServer에 때로는 인수가 없을 수 있고, 나중에 필요시에 필요한 처리를 작성한다"라고만 기억하도록 하자.

3. 요청 처리 설정

server.on('request', doRequest);

http.Server 객체에는 다양한 이벤트가 준비되어 있으며, 그 처리를 통합하는 방법도 준비되어 있다. “on"이라는 메소드는 지정된 이벤트 처리를 통합하는 것으로, 첫번째 인수에 이벤트 이름을, 두번째 인수에 통합 처리(함수)를 각각 지정한다.

여기에서는 'request'라는 이벤트에 doRequest라는 함수를 할당한다. request라는 것은 http.Server 객체가 클라이언트의 요청을 받았을 때 발생하는 이벤트로, 이를테면 여기에 “브라우저에서 서버에 액세스할 때 서버 응답 처리"를 포함한다.

4. 대기 시작

server.listen(1234);

http.Server 객체의 준비가 되면, “listen” 메소드를 실행한다. 그러면 서버는 대기 상태가 되고, 클라이언트에서 요청이 있으면 그것을 받아 처리할 수 있다. 인수는 포트 번호를 지정하고 있다. 두번째 인수로서 호스트 이름을 지정하거나, 세번째 인수에 백 로그를 지정하거나, 네번째 인수에 콜백 함수를 제공할 수도 있지만, 우선 “첫번째 인수에 포트 번호"인 것만 기억해두면 충분하다.

5. 콘솔에 출력

console.log( 'Server running!');

listen에서 대기 시작했던 곳으로 메시지를 콘솔에 출력한다. console은 콘솔 작업을 하기 위한 객체로 “log"로 로그 출력을 할 수 있다. 약간의 디버깅 용으로 로그 출력을 사용하면 편리하다.

요청 처리를 위한 함수

이것으로 http.Server 객체의 작성, 준비, 대기 시작까지의 흐름은 대체로 알 수 있었다. 남은 것은 “요청을 받았을 때의 처리"의 내용이다.

앞의 예제에서는 doRequest라는 함수를 정의해 두고, 이를 on 메소드에서 “request"이벤트에 조합했다. 이 request 이벤트에서 수행하는 작업이야말로 클라이언트의 요청을 처리하기 위한 것이다.

이 함수는 아래 같은 형태로 정의되어 있다.

function 함수명 (req, res) {
    ...... 필요한 처리 ......
}

인수는 두 객체가 전달된다. 각각 다음과 같다.

  • request

    • 첫번째 인수는 ‘request’ 객체가 전달된다. 이는 http.IncomingMessage라는 객체에서 클라이언트의 요청에 대한 기능을 정리하고 있다.
  • response

    • 두번째 인수는 “response"객체가 전단된다. 이는 http.serverResponse라는 객체에 서버에서 클라이언트로 리턴되는 응답에 대한 기능을 정리하고 있다.

이 request와 response를 사용하여, 요청을 받았을 때의 처리를 만든다. 여기에서는 다음과 같은 처리를 한다.

헤더 정보 내보내기

res.writeHead(200, {'Content-Type': 'text/plain'});

“writeHead"는 response 객체의 메소드에서 헤더 정보를 응답에 작성해서 내보내는 것이다. 첫번째 인자는 상태 코드를 지정하고 두번째인수에 헤더 정보를 연관 배열로 정리한 것이다.

여기에서는 {'Content-Type': 'text/plain'}값이 포함되어 있다. 이로 인해 Content-type이라는 헤더 정보에 'text/plain'의 값을 설정하고 있는 것을 알 수 있다. 이것은 응답으로 반송하는 콘텐츠의 종류를 나타내는 헤더 정보로, 이것으로 “이 콘텐츠는 표준 텍스트이다"라는 것이 클라이언트에 전달된다.

컨텐츠 내보내기

res.write('Hello World\n');

HTTP에는 헤더 정보의 다음에 바디 부분이 되는 콘텐츠를 작성하고 있는데, 이 내용 내보내기를 하고 있는 것이 response 객체의 “write"이다. 인수에 지정한 값이 바디 부분의 컨텐츠로 작성된다.

이 write는 여러번 호출 할 수 있다. 이것을 호출하여 작성을 하더라도, 아직 콘텐츠는 종료하지 않으므로, 계속해서 write으로 추가 작성할 수 있다.

컨텐츠 출력 완료(응답 종료)

res.end();

내용 내보내기가 완료되면 마지막으로 response의 “end"를 호출하여 콘텐츠 출력을 완료한다. 여기에서는 단지 end를 호출하고 있을 뿐이지만, 인수로 내보낼 내용의 값을 지정할 수 있다. 그러면 인수의 값을 쓴 후에 내용을 완료한다.

이 end로 인해 응답 처리는 종료되고, 그 요청의 처리가 완료된다. “writeHead”, “write”, “end"의 3개가 있으면, 클라이언트에 반환 내용은 모두 쓸 수 있다.

파일을 로드하는 fs 객체

그런데 response의 write로 내보낸다고 하더라도, 설마 write으로 HTML 코드를 모두 써내려가야만 하는가? 라고 생각한 사람이 있을 것이다. 그대로 표시하는 Web 페이지의 내용을 스크립트에서 문자열로 준비해야 한다면, 이는 있을 수 없는 일이다.

아무래도 표시할 페이지의 내용은 HTML 파일로 준비하고, 그것을 읽어 표시할 수 있도록 되어 있지 않으면 Web 아니다. 그럼, 여기에서는 이에 대해서 설명하겠다.

파일 로드하는 fs 객체

파일 로드는 “fs"라는 객체로 사용할 수 있다. require 함수에서 “fs"를 읽고, 그 안에 있는 메소드를 호출하여 로드한다. 다음과 같이 정리된다.

fs 객체 가져 오기

var fs = require('fs');

이미 http 객체 가져 오기는 해봤기에 알고 있을 것이다. fs 객체도 마찬가지로 읽어 변수에 할당해야 한다.

파일 가져 오기

fs.readFile( 파일의 경로 , 인코딩 , 콜백 함수 );

파일을 로드한다. 보통으로 생각하면 “readFile에서 읽은 데이터를 반환"이라고 상상하지만, 다르다. readFile는 반환 값이 없다. 왜냐하면 이것은 비동기적으로 실행되는 처리기 때문이다.

파일을 읽는 것은 시간이 걸리는 처리이다. 따라서 로딩이 끝나면 데이터를 반환하고 나서 진행이 된다면, 경우에 따라서는 매우 오래 기다리게 된다. 서버에서 그런 일을 하면 이전에 액세스한 사람의 파일을 로드가 완료까지 모두 기다리고 있어야 한다는 것이 된다.

그래서 읽기 시작하면 바로 다음 작업으로 진행하도록 설계가 되어 있는 것이다. 읽기 작업은 백그라운드에서 이루어 진다. 그리고 로드가 완료되면 미리 설정해둔 처리를 호출하고, “읽기 후의 처리"를 진행시킨다는 개념이다. 이 “작업이 끝나면 나중에 호출되는 함수"를 “callback 함수"라고 한다.

readFile에는 첫번째 인수에 가져올 파일의 경로를 지정하고, 두 번째 인수로 인코딩 이름, 그리고 세번재 인수에 로드 완료 후 콜백 함수를 지정한다. 로드 작업이 끝나면, 콜백 함수에서 처리를 하도록 하는 것이다.

HTML 파일 표시

그러면 실제로 HTML 파일을 읽어 들여 표시시켜 보기로 하자.

HTML파일을 읽어서 표시하기

먼저 로드 HTML 파일을 작성해야 한다. 여기에서는 “hello.html"이라는 파일명으로 만들어 보자. 아래 샘플 코드를 올려 두었다. 내용은 어떤 것이라도 전혀 상관 없다. 작성한 파일은 Node.js 스크립트 파일 (앞에서 만든 sampleapp.js)와 같은 위치에 배치해야 한다.

hello.html

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <title>sample</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">Sample Page</h1>
    </header>
    <div role="main">
        <p>이것은 Node.js 예제입니다.</p>
    </div>
</body>
 
</html>

이어서 스크립트를 작성한다. 예제는 아래와 같다. 스크립트를 작성한 후 명령 줄에서 “node sampleapp.js"를 실행하고 브라우저에서 액세스해 보자.

sampleapp.js

var http = require('http');
var fs = require('fs');
 
var server = http.createServer();
server.on('request', doRequest);
server.listen(1234);
console.log('Server running!');
 
// 요청 처리
function doRequest(req, res) {
    fs.readFile('./hello.html', 'UTF-8', 
        function(err, data) {
            res.writeHead(200, {'Content-Type': 'text/html'});
            res.write(data);
            res.end();
        });
}

hello.html 파일 내용의 페이지가 표시될 것이다.

여기에서는 요청시에 실행되는 doRequest 함수에서 hello.html을 로드하고 표시하는 처리를 하고 있다. 이 doRequest에서 로드 처리는 정리하면 다음과 같이 되어 있다.

fs.readFile('./hello.html', 'UTF-8', 
    function(err, data) {
            ... 읽기 완료 후에 처리 ...
    });

첫번째 인수에 “./hello.html"라고 파일 경로를 지정하고, 두번째 인수는 ‘UTF-8’을 지정한다. 그리고 세번재 인수에 읽기 완료 후에 처리를 할 콜백 함수가 준비되어 있다. 이 콜백 함수는 두 개의 인수가 준비되어 있다. 첫번째 인수는 오류가 발생한 경우 오류 메시지이다. 로드에 실패했을 경우의 처리를 준비한다면, 함수에서 if (err) {...}와 같은 형태로 적어 놓으면 좋을 것이다.

두번째 인수의 data에는 가져온 텍스트가 저장되어 있다. 이를 response의 write로 내내기 위하여 것이다. readFile의 콜백 함수의 처리를 보면,

res.writeHead(200, {'Content-Type': 'text/html'});
res.write(data);
res.end();

이와 같이 writeHead, write, end이 이 안에서 실행되고 있다. 이것은 중요하다. 예를 들어, 이 doRequest의 처리는, 아래와 같이 쓰고 싶을지도 모른다.

function doRequest(req, res) {
    res.writeHead(200, {'Content-Type': 'text/html'});
    fs.readFile('./hello.html', 'UTF-8', 
        function(err, data) {
            res.write(data);
        });
    res.end();
}

읽어 들인 데이터를 출력하는 write만 readFile의 콜백 함수에 준비하면 된다고 생각할 있다. 하지만 이것으로는 동작하지 않는다. “readFile는 비동기이다"라는 점을 잊어서는 안된다. readFile 콜백 함수를 호출 이전에 다음의 res.end 실행되어 버릴 것이다. 콜백 함수의 res.write을 실행할 때는 이미 요청 처리가 완료되어 버릴 것이다.

이렇게 비동기적으로 수행하는 처리가 포함되어 있을 때에는 “비동기 작업이 완료한 후에 응답 처리를 한다"는 점에 주의해야 한다.

HTML의 일부를 스크립트로 변경

단지, HTML 파일을 읽어 들여 표시하는 것만으로는 그다지 서버 사이드 프로그램을 만드는 장점이 느껴지지 않는다. 거기서 읽어들인 HTML을 기초로 하여 작은 텍스트 처리를 실시하도록 하자.

템플릿적인 개념이 먼저 표시되는 HTML에서, 텍스트를 대체하기 위한 특별한 값를 넣어, 그것을 로드하고 표시하는데 필요한 내용으로 대체하여 출력하는 방법을 생각할 수 있다. 해보도록 하자.

먼저 hello.html을 수정한다.

hello.html

<!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>

기본적으로 앞전과 거의 동일하지만, 소스 코드 안에 “@title@“와 “@content@“라는 것이 작성이 있는 것을 보일 것이다. 이것이 대체용 특수값이다(별도로 Node.js에 그러한 특별한 값이 정의되고 있을 일이 없다. 적당히 생각한 값이다). 스크립트에는 이러한 텍스트를 검색하고 대체하여, 스크립트로 HTML 내에서 연산할 수 있을 것이다.

Node.js의 스크립트를 다음과 같다.
sampleapp.js

var http = require('http');
var fs = require('fs');
 
var server = http.createServer();
server.on('request', doRequest);
server.listen(1234);
console.log('Server running!');
 
// 요청 처리
function doRequest(req, res) {
    var number = Math.floor(Math.random() * 3);
    fs.readFile('./hello.html', 'UTF-8', 
        function(err, data) {
            var title = ["페이지A", "페이지B", "페이지C"];
            var content = ["이것은 예제로 만든 것입니다.",
                "또 하나의 컨텐츠입니다.",
                "최후에 이용한 컨텐츠입니다."];
            var data2 = data.
                replace(/@title@/g, title[number]).
                replace(/@content@/g, content[number]);
            res.writeHead(200, {'Content-Type': 'text/html'});
            res.write(data2);
            res.end();
        });
}

여기에는 텍스트를 읽은 후에 replace에서 텍스트 바꾸기를 하고 있다.

var data2 = data.replace(/@title@/g, title[number]).
        replace(/@content@/g, content[number]);

이렇게 하여 @title@@content@를 각각 배열 title/content에서 무작위로 선택한 것으로 대체하고 write하고 있다. 페이지를 새로 고침할 때마다, 랜덤으로 선택된 텍스트가 표시되는걸 확인할 수 있을 것이다.

또한, 여기에서는 비동기적으로 파일을 로드하는 readFile를 사용했는데, 동기화 처리에 파일을 읽어들이는 메소드도 fs에 포함되어 있다. 이것에 대해서는 다음에 다시 접하게 될 것이다.

우선, 여기에는 적은 값을 스크립트에서 HTML 내에 포함 할 수 있었다. 그러나 이것으로는 너무 융통성은 없다. 좀 더 범용적으로 표시를 생성하고 조작할 수 없으면 곤란하다. 그럼 다음은 “템플릿 엔진"이라는 것을 사용하여 좀 더 편리하게 HTML을 사용할 수 있도록 해보자.




최종 수정 : 2018-07-16