Streaming 서비스
이번강좌에서는 node.js 의 강점이랄 수 있는 대용량 streaming 처리에 대해 알아보겠습니다. node.js 는 이벤트 Loop 기반의 비동 처리를 지원하기 때문에 대용량 파일을 구간별로 작게 나누어서 처리 하는 작업에 강점을 가지고 있습니다.
가. 일반적인 readFile( ) 의 문제점
아래의 소스코드를 한번보겠습니다. fs.readFile( ) 을 이용해서 'movie.mp4' 라는 바이너리 파일을 서비스 하는 코드입니다. 사이즈가 아주 큰(적어도 100MB 이상되는..) 동영상 파일을 서비스 한다고 생각해보죠. 아래 코드는 서버에서 파일을 다 읽은 후에 파일읽기가 완료가 되면 클라이언트로 한번에 전송하게 됩니다. 그럼 어떻게 될까요? 클라이언트에는 서버로부터 데이터를 전송받을 때 까지 대기를 하게 됩니다. 용량이 커질수록 클라이언트의 대기시간도 길어질 뿐더러 요청이 많아질수록 서버의 효율이 떨어지게 될겁니다.
fs.readFile('movie.mp4', function(error, data) {
request.end(data);
});
이런 단점을 보완하기 위해 데이터를 전체를 다 읽거나 쓰지 않아도 중간에 처리할 수 있도록 해주는 것이 stream 입니다. 현재 인터넷에서 서비스 되고 있거나 오픈소스로 지원하는 streaming 라이브러리가 많이 있지만 node.js 는 아주 적은량의 코딩만으로 이런 streaming 서비스를 쉽게 구현할 수 있습니다.
나. 예제 실행을 위한 동영상 파일 다운로드
먼저 용량이 큰 동영상 파일을 하나 준비해야 합니다. 유튜브 같은 곳을 동영상 사이트를 통해서 어렵지 않게 구하실 수 있으리라 생각됩니다.
프로젝트 폴더아래에 movie 라는 폴더를 새로 만들고, 해당 동영상 파일을 복사해둡니다. 임시로 800MB 용량의 mp4 파일을 다운로드 받아서 movie 폴더에 big.mp4 로 저장해두었습니다.
- movie/big.mp4
다. 동영상 실행을 위한 플레이어 HTML
그리고 웹브라우저에서 동영상 파일을 재생하기 위해 HTML5의 멀티미디어 재생 태그인 <video>를 사용해서 저장해둔 big.mp4 파일을 실행하는 형태로 소스코드를 작성해 보겠습니다. 먼저 각 리소스를 분리하기 위해 html 폴더를 하나 생성합니다. 그리고 html 폴더 아래에 movie_player.html 파일을 만들고 아래와 같이 입력합니다.
- html/movie_player.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<title>Movie Player</title>
</head>
<body>
<h1>Movie Player</h1>
<video width="320" height="240" controls>
<source src="movie/big.mp4" type="video/mp4">
Your browser does not support the video tag.
</video>
</body>
</html>
위의 html 파일에서 video 폴더에 있는 big.mp4 파일을 실행해 줄것입니다.
라. 동영상 파일을 Streaming 서비스 하기위한 소스코드
이제 streaming_movie.js 파일을 생성하고 아래와 같이 입력합니다.
- streaming_movie.js
var http = require('http');
var url = require('url');
var fs = require('fs');
var server = http.createServer(function(request,response){
var parsedUrl = url.parse(request.url);
var resource = parsedUrl.pathname;
console.log('resource='+resource);
var resourcePath = '.'+resource;
console.log('resourcePath='+resourcePath);
// html 페이지 요청이 들어왔을 경우는 텍스트 파일 처리
if(resource.indexOf('/html/') == 0){
fs.readFile(resourcePath, 'utf-8', function(error, data) {
if(error){
response.writeHead(500, {'Content-Type':'text/html'});
response.end('500 Internal Server '+error);
}else{
response.writeHead(200, {'Content-Type':'text/html'});
response.end(data);
}
});
}else if(resource.indexOf('/movie/') == 0){
// 1. stream 생성
var stream = fs.createReadStream(resourcePath);
// 2. 잘게 쪼개진 stream 이 몇번 전송되는지 확인하기 위한 count
var count = 0;
// 3. 잘게 쪼개진 data를 전송할 수 있으면 data 이벤트 발생
stream.on('data', function(data) {
count = count + 1;
console.log('data count='+count);
// 3.1. data 이벤트가 발생되면 해당 data를 클라이언트로 전송
response.write(data);
});
// 4. 데이터 전송이 완료되면 end 이벤트 발생
stream.on('end', function () {
console.log('end streaming');
// 4.1. 클라이언트에 전송완료를 알림
response.end();
});
// 5. 스트림도중 에러 발생시 error 이벤트 발생
stream.on('error', function(err) {
console.log(err);
// 5.2. 클라이언트로 에러메시지를 전달하고 전송완료
response.end('500 Internal Server '+err);
});
}else{
response.writeHead(404, {'Content-Type':'text/html'});
response.end('404 Page Not Found');
}
});
server.listen(80, function(){
console.log('Server is running...');
});
마. 폴더 및 파일구조
아래와 같은 구조의 폴더와 파일이 생성되어 있어야 합니다.
- 프로젝트ROOT/streaming_movie.js
- 프로젝트ROOT/html/movie_player.html
- 프로젝트ROOT/movie/big.mp4
바. 실행 및 브라우저를 통한 확인
node streaming_movie 를 실행한 후에 브라우저에서 http://localhost/html/movie\_player.html 을 호출해봅니다. 브라우저에 플레이어가 출력되는 데 플레이 버튼을 클릭하면 정상적으로 실행되는 것을 확인할 수 있습니다.
그리고 CMD 창을 확인하면 아래와 같이 로그가 대량으로 발생해 있는것을 확인할 수 있습니다. 데이터를 잘게 나누어서 전송가능한 양이 되면 stream의 data 이벤트를 발생하기 때문에 800MB 용량의 파일은 로그에서 보듯이 10,000개 이상의 파일로 잘게 나누어서 전송을 하게 되는 것입니다.