본 문서(node.js 애플리케이션 개발 가이드)는 개방형 플랫폼 프로젝트의 서비스팩(Mysql, Cubrid, MongoDB, RabbitMQ, Radis, GlusterFS)을 Node.js 애플리케이션과 연동하여서비스를 사용하고 애플리케이션을 배포하는 방법에 대해 제시하는 문서이다.
1.2. 범위
본 문서의 범위는 개방형 플랫폼 프로젝트의 Node.js 애플리케이션 개발과 서비스팩 연동에 대한 내용으로 한정되어 있다.
1.3. 참고자료
2. 개발환경 구성
Open PaaS에 등록된 다양한 서비스팩을 Node.js언어로 작성된 애플리케이션과 바인딩하고해당 애플리케이션에 바인딩된 환경정보(VCAP_SERVICES)에서 각 서비스별 접속정보를 획득하여 애플리케이션에 적용하여 이용 할 수 있도록 Windows 환경에서 Node.js 애플리케이션을 작성 할 수 있도록한다.
Node.js 애플리케이션 개발을 위해 다음과 같은 환경으로 개발환경을 구성 한다.
BuildPack: v1.3.4
OS : Windows 7 64bit
Node.js : v0.12.4
npm : v2.10.1
2.1. Node.js 및 npm 설치
1. Node.js 다운로드
아래의 주소로 접속한 후 node-v0.12.4-x64.msi를 다운받는다.
2. Node.js 설치
다운받은 폴더에서 node-v0.12.4-x64.msi를 더블클릭하여 설치를 시작한다.
"실행"버튼을 클릭하여 계속 진행한다.
"Next"버튼을 클릭하여 계속 진행한다.
"I accept the terms in the License Agreement"를 체크하여 라이센스에 동의한 후 "Next"버튼을 클릭하여 계속 진행한다.
설치경로를 입력 혹은 선택한 후 "Next"버튼을 클릭하여 계속 진행한다.
여기서는 C:\Program Files\nodejs 를 설치경로로 설정하였다.
설치할 항목을 선택한 후 "Next"버튼을 클릭하여 계속 진행한다.
여기서는 선택하여 Node.js, npm, doc을 설치하고 환경변수 PATH까지 추가하였다.
"Install"버튼을 클릭하여 설치한다.
"Finish"버튼을 클릭하여 설치를 완료한다.
'윈도우키+R' 또는 '시작->실행'아이콘을 클릭하여 실행창을 띄운 후 'cmd'를 입력하고 "확인"버튼을 눌러 커맨드창을 연다.
커맨드창에 아래의 명령어를 입력하여 node.js와 npm의 버젼과 제대로 설치되었는지 여부를 확인한다.
>node -v
개발도구 Node.js는 javascript기반의 언어로 Notepad++, Sublim Text, EditPlus등 문서편집기를 개발도구로 사용할 수 있다. 또한 Eclipse의 플러그인 Nodeclipse를 설치하여 사용할 수도있다.
3. 개발
샘플 애플리케이션에의 데이터 관리는 MySQL, CubridDB, MongoDB 중에 하나를 이용하기 때문에 API 요청시 요청 본문에 DBType 값을 가지고 결정한다.
3.1. Node.js Express애플리케이션 생성
1. 'express-generator'를 이용하여 Express 애플리케이션을 생성
커맨드 창에서 개발을 진행할 경로로 이동후 아래의 명령어를 입력하여 'express-generator' npm을 설치한다.
Express 애플리케이션에 기본적으로 포함되어있는 npm을 설치한다. 설치할 npm에 대한 정의는 package.json에 정의되어있다.
3. Node.js Express 어플리캐이션 실행
아래의 두 명령어중 하나를 이용해 애플리케이션 실행한다.
>npm start
브라우저로 아래의 주소로 접속하여 애플리케이션이 제대로 동작하는지 확인한다.
3.2. Node.js 샘플 애플리케이션
1. Node.js 샘플 애플리케이션 다운로드
완성된 샘플 애플리케이션은 아래 링크의 /OpenPaaSSample/node-sample-app 에서 받을 수 있다.
2. Node.js 샘플 애플리케이션 경로로 이동
다운받은 경로아래에 Node.js 샘플 애플리케이션 경로로 이동한다.
3. Node.js 샘플 애플리케이션 디렉토리구조
파일/폴더
목적
package.json
node.js 어플리케이션에 필요한 npm의 의존성 정보를 기술하는데 사용 한다.
npm install 명령을 실행시 install 뒤에 아무런 정보를 입력하지 않으면 이 파일의 정보를 이용하여 npm을 설치한다.
app.js
Express에 대한 설정을 한다. http요청에 대한 라우팅 정보 또한 이곳에서 정의한다.
bin/www
실질적으로 node.js 샘플 애플리케이션의 시작점이며, http서버를 설정한다. 서버가 구동될 때 사용할 Port를 여기서 설정할 수 있다.
routes/
app.js에서 라우팅 후 실제 수행할 내용이 작성되어있다. 서비스 연결에 대한 내용도 이곳에 있다.
public/
외부에서 접근가능한 디렉토리이다. css, js등 웹서비스에 필요한 정적파일들이 있다.
외부에서 접근가능한 디렉토리에 대한 설정은 app.js파일에서 설정한다.
views/
ejs파일들이 위치하는 곳이다.
ejs파일은 html을 좀더 쉽게 작성할 수 있게 도와주는 template enjin 이며 express의 view enjin을 ejs로 설정했을시 사용하는 파일이다.
render() 메소드를 이용하여 ejs파일을 html로 변환하여 보여준다.
test/
mocha test를 작성한 디렉토리이다.
(node_modules)
위의 그림에서는 보이지 않지만 npm install로 모듈설치시 이 디렉토리아래에 설치가 된다. 여기에 설치되어있는 npm모듈들을 애플리케이션에서 require로 불러와서 사용할 수 있다.
Makefile
linux에서 좀더 쉽게 mocha 테스트를 실행하기위한 파일이다.
manifest.yml
개방형 플랫폼에 배포시 애플리케이션에 대한 설정이다. 애플리케이션의 이름, 배보될 경로, 인스턴스 수 등을 정의할 수 있다.
.cfignore
개방형 플랫폼에 배포시 포함되지않을 디렉토리, 혹은 파일을 기술한다.
.gitignore
git에 배포시 포함되지않을 디렉토리, 혹은 파일을 기술한다.
README.md
Node.js 샘플 애플리케이션에 대한 간략한 설명이 기술되어 있다.
3.3. 애플리케이션 환경설정
이 샘플은 Node.js version 0.12.4, npm version 2.10.1.을 기준으로 각 모듈의 버전을 명시적으로 선택하여 설치하였다. package.json 수정(설정)시 설치된 Node.js의 버전에 맞는 모듈을 설치하는 것을 권장한다.
애플리케이션에서 사용할 node.js 모듈 버젼.
개방형 플랫폼에 배포하여 사용시 Node Buildpack에서 지원하는 Node.js 버젼에 따라 사용할 수 있는 버젼에 제약이 있다.
- https://github.com/cloudfoundry/nodejs-buildpack/blob/master/CHANGELOG
npm
애플리케이션에서 사용할 npm 버젼
Node.js와 마찬가지로 Node Buildpack에서 지원하는 버젼에 따라 사용할 수 있는 버젼에 제약이 있다.
2) 모듈 설치
pakage.json에 정의된 모듈을 설치한다. 모듈이름을 지정하지 않으면 package.json의 depencencies의 모든 모듈을 설치한다.
>npm install
3) ./bin/www
HTTP서버가 사용할 PORT를 개방형 플랫폼이 제공하는 PORT를 사용하게 설정한다. 개방형 플랫폼은 이 값을 이용하여 애플리케이션이 제대로 동작하고 있는지 감지하는데 사용한다. 이 값 외의 다른 PORT를 사용하면 애플리케이션이 제대로 동작하지 않는다.
#!/usr/bin/env node
/**
* Module dependencies.
*/
var app = require('../app');
var debug = require('debug')('node-sample-app:server');
var http = require('http');
/**
* Get port from environment and store in Express.
* port 환경변수를 얻어와서 변수에 담는다.
* 'process.env.PORT'는 Cloud에서 사용하는 환경변수.
*/
var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);
/**
* Create HTTP server.
* HTTP 서버 생성.
*/
var server = http.createServer(app).listen(app.get('port'), function(){
console.log('Express server listening on port ' + app.get('port'));
});
/**
* Listen on provided port, on all network interfaces.
* 서버가 사용할 port를 설정한다.
*/
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
...(생략)
4) ./app.js
Request URL 매핑 설정
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var routes = require('./routes/index');
var users = require('./routes/users');
var app = express();
// URL 매핑 후 수행할 js 파일들
var org_chart_mysql = require('./routes/rest/org_chart/mysql')
, org_chart_mongo = require('./routes/rest/org_chart/mongo')
, org_chart_cubrid = require('./routes/rest/org_chart/cubrid')
, orgs_mysql = require('./routes/rest/orgs/mysql')
, orgs_mongo = require('./routes/rest/orgs/mongo')
, orgs_cubrid = require('./routes/rest/orgs/cubrid')
, groups_mysql = require('./routes/rest/groups/mysql')
, groups_mongo = require('./routes/rest/groups/mongo')
, groups_cubrid = require('./routes/rest/groups/cubrid')
, login = require('./routes/rest/login/login')
, image = require('./routes/rest/image/image')
, page = require('./routes/page/page_processing');
// view engine setup
// 뷰 엔진 설정
app.set('views', path.join(__dirname, 'views/'));
app.set('view engine', 'ejs');
// uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
// 정적파일 위치 설정
app.use(express.static(path.join(__dirname, 'public')));
/*
* URL 매핑 설정
*/
app.use( '/', routes);
app.use( '/users', users);
// page
app.get( '/login', page.login);
app.get( '/manage', page.manage);
app.get( '/main/:id', page.main);
// org-chart
app.get( '/org-chart/:org_id/mysql', org_chart_mysql.index);
app.get( '/org-chart/:org_id/cubrid', org_chart_cubrid.index);
app.get( '/org-chart/:org_id/mongo', org_chart_mongo.index);
app.get( '/org-chart/:org_id/status/mysql', org_chart_mysql.status);
app.get( '/org-chart/:org_id/status/cubrid', org_chart_cubrid.status);
app.get( '/org-chart/:org_id/status/mongo', org_chart_mongo.status);
//orgs
// mysql
app.get( '/orgs/mysql', orgs_mysql.index);
app.post( '/orgs/mysql', orgs_mysql.create);
app.get( '/orgs/:org_id/mysql', orgs_mysql.show);
app.put( '/orgs/:org_id/mysql', orgs_mysql.update);
app.delete('/orgs/:org_id/mysql', orgs_mysql.destroy);
…..(생략)
3.4. VCAP_SERVICES 환경설정 정보
개방형 플랫폼에 배포되는 애플리케이션이 바인딩된서비스별 접속 정보를 얻기 위해서는 애플리케이션별로 등록되어있는 VCAP_SERVICES 환경설정 정보를 읽어들여정보를 획득 할 수 있다.
개방형 플랫폼의 애플리케이션 환경정보에 접근하여 mysql Connection Pool을 생성
```javascript
/**
generic-pool 연동
mysql 풀 모듈 구현
*/
var generic_pool = require("generic-pool"); var mysql = require("mysql");
config = {}; if (process.env.VCAP_SERVICES) { // cloud env 설정. 데이터 구조는 2.3.4 VCAP_SERVICES 환경정보 참고 var cloud_env = JSON.parse(process.env.VCAP_SERVICES); var mysql_env = cloud_env["Mysql-DB"][0]["credentials"];
### <div id='13'> 3.6. Cubrid 연동
1) ./route/db/cubrid/db_pooling.js
- 개방형 플랫폼의 애플리케이션 환경정보에 접근하여 cubrid Connection Pool을 생성
```javascript
/**
* generic-pool 연동
* cubrid 풀 모듈 구현
*/
var generic_pool = require("generic-pool");
var cubrid = require("node-cubrid");
var database
, port
, hostname
, username
, password;
if (process.env.VCAP_SERVICES) {
// cloud env 설정. 데이터 구조는 2.3.4 VCAP_SERVICES 환경정보 참고
var cloud_env = JSON.parse(process.env.VCAP_SERVICES);
var cubrid_env = cloud_env["CubridDB"][0]["credentials"];
database = cubrid_env.name
port = cubrid_env.port
hostname = cubrid_env.hostname
username = cubrid_env.username
password = cubrid_env.password;
} else {
// local env
database = 'fccf1d7869ff72ce'
port = ''
hostname = '10.30.60.23'
username = 'b2f6b4af1e7bd7d8'
password = '45f179c648ee60a5';
}
var pooling = generic_pool.Pool({
name:"cubrid",
create:function(cb){
// console.log("cubrid_env.uri:" + cubrid_env.uri);
var conn = cubrid.createCUBRIDConnection(hostname, port, username, password, database);
// create Connection
conn.connect(function(err){
if( err) console.log("cubrid 연결오류");
else{
// console.log("cubrid 연결성공");
cb(err, conn);
}
// 콜백함수를 통해 풀링에 커넥션 객체를 던짐
});
},
destroy:function(myConn){
myConn.end(function(err){
if( err) console.log("cubrid 연결해제오류");
// else console.log("cubrid 연결해제성공");
});
},
min:3,
max:5,
idleTimeoutMillis:1000*50,
log:false,
});
process.on("exit", function(){
pooling.drain(function(){
pooling.destroyAllNow();
});
});
module.exports = pooling;
3.7. MongoDB 연동
1) ./route/db/mongo/db_pooling.js
개방형 플랫폼의 애플리케이션 환경정보에 접근하여 mongodb Connection Pool을 생성
/**
* generic-pool 연동
* mongo 풀 모듈 구현
*/
var generic_pool = require("generic-pool");
var mongoClient = require("mongodb").MongoClient;
var url = '';
if (process.env.VCAP_SERVICES) {
// cloud env. 설정. 데이터 구조는 2.3.4 VCAP_SERVICES 환경정보 참고
var cloud_env = JSON.parse(process.env.VCAP_SERVICES);
var mongo_env = cloud_env["Mongo-DB"][0]["credentials"];
url = mongo_env.uri;
} else {
// local env.
url = 'mongodb://d3e35ad5-9f49-43ae-bc85-08e39ec1d8eb:fc23791e-b2d8-402d-b070-90bfbdb5dcfa@10.30.60.53:27017/e37e541c-75de-4f01-8196-63e2d902e768';
}
var pooling = generic_pool.Pool({
name:"mongo",
create:function(cb){
// create Connection
mongoClient.connect(url, function(err, db){
if (err) console.log("mongo 연결오류");
else {
cb(err, db);
}
});
},
destroy:function(myDb){
myDb.close(function(err){
if( err) console.log("mysql 연결해제오류");
// else console.log("mysql 연결해제성공");
});
},
min:3,
max:5,
idleTimeoutMillis:1000*500,
log:false
});
module.exports = pooling;
3.8. Redis 연동
1) ./route/redis/redis.js
개방형 플랫폼의 애플리케이션 환경정보에 접근하여 redis Connection을 생성
var redis = require("redis");
var options = {};
if (process.env.VCAP_SERVICES) {
// cloud env. 설정. 데이터 구조는 2.3.4 VCAP_SERVICES 환경정보 참고
var services = JSON.parse(process.env.VCAP_SERVICES);
var redisConfig = services["redis-sb"];
if (redisConfig) {
var node = redisConfig[0];
options = {
host: node.credentials.host,
port: node.credentials.port,
pass: node.credentials.password,
};
}
} else {
// local env.
options = {
host: '10.30.40.71',
port: '34838',
pass: 'c239b721-d986-4ee3-8816-b5f5fa9f3ffb',
};
}
var client = null;
exports.open = function(cb) {
// console.log(JSON.stringify(options));
//create Client
client = redis.createClient(options);
// get auth.
client.auth(options.pass);
cb(client);
}
exports.close = function(){
client.end();
}
3.9. RabbitMQ연동
1) ./route/rabbitMQ/rabbitMQ.js
개방형 플랫폼의 애플리케이션 환경정보에 접근하여 rabbirMQ Connection을 생성
var amqp = require('amqp');
var url = '';
if (process.env.VCAP_SERVICES) {
// cloud env. 설정. 데이터 구조는 2.3.4 VCAP_SERVICES 환경정보 참고
var services = JSON.parse(process.env.VCAP_SERVICES);
var rabbitMQConfig = services["p-rabbitmq"];
if (rabbitMQConfig) {
var node = rabbitMQConfig[0];
url = node.credentials.uri;
}
} else {
// local env.
url = 'amqps://14b1ab93-4cdb-46af-8cdd-8d8073bbe282:cl71e9ihgu6gvhj1eiqj9uh4um@10.30.40.82:5671/6ffb4d8a-8748-4f00-a338-80e6eadee822';
}
exports.open = function(cb){
// create connection.
var conn = amqp.createConnection({url: url});
// it must be cb(callback) after the 'ready' event.
conn.on('ready', function(){
cb(conn);
});
}
// not used.
/*
exports.close = function(){
conn.disconnect();
}
*/
3.10. GlusterFS 연동
1) ./route/glusterfs/glusterfs.js
개방형 플랫폼의 애플리케이션 환경정보에 접근하여 glusterfs Connection을 생성
var pkgcloud = require('pkgcloud');
var http = require('http');
var url = require('url');
var credentials = {};
var container_name = 'node_container';
if (process.env.VCAP_SERVICES) {
// cloud env. 설정. 데이터 구조는 2.3.4 VCAP_SERVICES 환경정보 참고
var services = JSON.parse(process.env.VCAP_SERVICES);
var glusterfsConfig = services["glusterfs"];
if (glusterfsConfig) {
var config = glusterfsConfig[0];
credentials = {
provider: 'openstack', //
username: config.credentials.username,
password: config.credentials.password,
authUrl: config.credentials.auth_url.substring(0, config.credentials.auth_url.lastIndexOf('/')),
region: 'RegionOne' //
};
}
} else {
// local env.
credentials = {
provider: 'openstack',
username: 'cf13d551d997458e',
password: 'b45cc01d53a4f0e0',
authUrl: 'http://54.199.136.22:5000/',
region: 'RegionOne'
};
}
// create Client
var client = pkgcloud.storage.createClient(credentials);
// delete container for test
/*
client.destroyContainer(container_name, function(err, result){
if (err) console.log(err);
else console.log(result);
});
*/
// check container
client.getContainer(container_name, function(err, container){
if (err)
{
// if container not exist
if (err.statusCode === 404)
{
// create container
client.createContainer({name:container_name}, function(create_err, create_container){
if (create_err) console.log(err);
else
{
// if container created successfully, setting a readable member(X-Contaner-Read: .r:*)
// 컨테이너가 성공적으로 생성되었다면 컨테이너를 누구나 읽을 수 있게 설정한다.(X-Contaner-Read: .r:*)
// There is a bug in the code(pkgcloud). so i used api call.
// pkgcloud 모듈에서 metadata를 넣을 경우 prefix가 붙는 로직때문에 제대로 위의 값이 입력이 안되므로 api를 통해서 설정.
var serviceUrl = url.parse(create_container.client._serviceUrl);
var option = {
host: serviceUrl.hostname,
port: serviceUrl.port,
path: serviceUrl.path+'/'+container_name,
method: 'POST',
headers: {
'X-Auth-Token': create_container.client._identity.token.id,
'X-Container-Read': '.r:*' // ACL form
}
};
var req = http.request(option, function(res){
});
req.end();
}
});
}
else console.log(err);
}
});
module.exports = client;
4. 배포
개방형 플랫폼에 애플리케이션을 배포하면 배포한 애플리케이션과 개방형 플랫폼이 제공하는 서비스를 연결하여 사용할 수 있다. 개방형 플랫폼상에서 실행을 해야만 개방형 플랫폼의 애플리케이션 환경변수에 접근하여 서비스에 접속할 수 있다.