잊지 않겠습니다.

Express에서의 Session 사용법

기초중의 기초이지만, 간혹 사용법이 헛갈릴때가 많아서 정리하였습니다.

요구사항

  1. ES6 문법을 기초로 이용
  2. Session을 이용한 로그인 관리
  3. Cluster를 이용가능 (pm2 등을 이용)
  4. login을 통해 얻어진 사용자 이름을 session에 저장.
  5. view는 모두 json을 이용

개발 환경 구성

yo generator중에 generator-express 설치

npm install -g generator-express

특정 directory에서 yo express 실행하면 기본적인 express application이 구성됩니다. 구성된 폴더안에서 gulp default를 실행하면 기본 port 3000번으로 application이 구동됩니다.

Application이 구동되지 않을때.

port 35729가 사용중일때 : gulp.liveload를 이용한 다른 application이 구동되고 있을 때, 발생되는 error. 두개의 application을 동시에 실행하고 싶은 경우에는 gulpfile.js에서 liveload method에 port 번호를 명시. livereload.listen(60000);

config.root undefined가 될때. config/config.js가 정상적으로 로드되지 않은 경우. NODE_ENVdevelopment, production 등으로 지정하지 않은 경우에 발생. 자신의 NODE_ENV를 확인후, NODE_ENV에 맞게 config.js를 수정

추가 npm - express-session, filestore

Session을 이용하기 위해서 express-session 설치합니다. 구 버젼을 기반으로한 책이나 blog에서는 이 과정이 나오지 않습니다. express의 middleware들은 대부분 npm module로 따로 분리가 되어 사용됩니다. express에 기본으로 들어가 있는 경우는 얼마 없습니다.

npm install express-session --save
babel-express-app@0.0.1 /home/ykyoon/dev/code/babel-express-app
└─┬ express-session@1.13.0 
  ├── crc@3.4.0 
  └─┬ uid-safe@2.0.0 
    └── base64-url@1.2.1 

npm WARN optional Skipping failed optional dependency /chokidar/fsevents:
npm WARN notsup Not compatible with your operating system or architecture: fsevents@1.0.7

express.js파일에 express-session을 middleware로 등록합니다.

const session = require('express-session');
app.use(session({
    secret: 'keyboard cat',
    resave: false,
    saveUninitialized: false,
    cookie: { secure: false }
}));

지정되는 parameter는 다음 의미를 갖습니다.

  • secret: session의 암호화에 사용되는 key값입니다.
  • resave: requrest가 요청되었을때, 기존의 session이 존재하는 경우 다시 저장할 필요가 있는지를 확인하는 option입니다. express-session에서 제공되는 touch를 구현하고 있는지를 확인해보면 됩니다. 기본적인 MemoryStore의 경우, touch가 구현되어 있기 때문에 설정할 필요가 없습니다.
  • saveUninitialized: 설정하지 않는 경우, Session이 저장되지 않습니다. 일반적인 로그인을 구성할 때, false로 지정해줘야지 됩니다. 기본값은 true이며 모든 초기화되지 않은 session은 저장되게 됩니다.
  • cookie.secure: https로 호출되는 경우에만 session cookie를 생성하는 option입니다. 실 production에 유용한 option으로 테스트나 개인 공부를 할 때는 false로 지정해주시면 됩니다.
  • cookie.maxAge: cookie가 만료되는 시간을 설정합니다.

login url 구현

DB를 사용하지 않는 최소한의 application이기 때문에 사용자가 입력한 username을 그대로 session의 username으로 넣어주는 code를 구성합니다.

'use strict';

const express = require('express'),
  router = express.Router();

router.get('/login', (req, res) => {
  const session = req.session;
  session.username = req.query.username;
  return res.json(session);
});

module.exports = (app) => {
  app.use('/user', router);
};

원래는 login의 경우, post로 구현해야지 되는 것이 원칙이지만, 테스트를 위한 application이고, browser에서 그대로 구현하기 위해서이니 그냥 get으로 구현하였습니다. 이제 저장된 username을 얻어내야지 됩니다. 이제 browser에서는 저장된 username이 계속 유지가 되어야지 됩니다.

router.get('/info', (req, res) => {
  return res.json(req.session);
});

이제 url을 두개를 연달아 browser에서 실행하면 다음과 같은 결과를 볼 수 있습니다.

{
    "cookie":{"originalMaxAge":null,"expires":null,"secure":false,"httpOnly":true,"path":"/"},
    "username":"abcdef"
}

Session store의 변경

고성능, 고가용성을 구현하기 위해서는 기본적으로 nodejs는 cpu의 core 숫자와 동일한 process숫자로 실행되어야지 됩니다. 이 사실은 지금 application의 경우 심각한 문제를 가지고 있는 것을 의미합니다. process간의 memory 공유는 원칙적으로 되지 않는거니까요. (hacking이나 memory editor의 경우 다르겠지만, web application의 기본 기능과는 거리가 있는 상황이니.)

그래서, nodejs application에서는 공용으로 사용되어야지 되는 정보는 file이나 redis, db와 같은 공용 IO에 저장해서 모든 process가 공유하는 형식으로 사용하게 됩니다. 또한, nodejs application은 application을 종료했다가 다시 올리는 경우가 왕왕 있습니다. express-session의 기본값인 MemoryStore는 기본적으로 production에 올릴수 없는 설정입니다.

성능은 안좋고, multiple instance에서는 절대로 사용할 수 없는 방법이지만, File에 session정보를 저장하기 위해서 FileStore를 이용해보도록 하겠습니다. FileStore와 Session간의 interface를 하는 session-file-store를 설치하면 FileStore가 같이 설치됩니다.

npm install session-file-store --save
babel-express-app@0.0.1 /home/ykyoon/dev/code/babel-express-app
├── filestore@0.1.3 
└─┬ session-file-store@0.0.24 
  ├── bagpipe@0.3.5 
  ├─┬ fs-extra@0.26.5 
  │ ├── jsonfile@2.2.3 
  │ ├── klaw@1.1.3 
  │ └─┬ rimraf@2.5.2 
  │   └── glob@7.0.0 
  └── retry@0.8.0

FileStore를 Session에 설정합니다. 코드는 다음과 같습니다.

const session = require('express-session');
const FileStore = require('session-file-store')(session);
app.use(session({
  secret: 'keyboard cat',
  resave: false,
  saveUninitialized: false,
  cookie: { secure: false },
  store: new FileStore()
}));

설치후, folder를 보면 session라는 folder가 생성되어 있는 것을 볼 수 있습니다. 이제 위에서 실행했던 URL을 다시 실행해보면 File이 하나 생성되어 있는 것을 볼 수 있습니다. 파일을 열어보면, session의 정보가 그대로 저장되어 있는 것을 볼 수 있습니다.

정리

express에서 session을 구성하는 방법에 대해서 간단히 알아봤습니다. 또한 MemoryStore가 아닌 다른 Store를 이용하는 방법에 대해서도 간단히 알아 보았습니다. 실무에서는 대부분 redis를 이용하는 경우가 일반적이긴 하지만, 개발단에서 편하게 알아보기 위해서, File을 사용하는 경우도 꼭 나쁜 것은 아닙니다.


저작자 표시 동일 조건 변경 허락
신고
Posted by xyzlast Y2K

mocha를 이용한 express application test 방법

yo-express를 이용한 application을 test 하는 방법을 한번 알아보고자합니다.

npm module 설치

web에 대한 test는 supertest를 사용하는 것이 좋습니다. 직관적인 사용에도 좋고, mocha에서 사용하기에도 매우 편합니다.

npm install supertest --save-dev

root에 test folder 생성

app.test.js 파일 생성

express application을 먼저 구동하는 것이 필요합니다. 이는 원 app.js에서 구성되는 express start와 sequelize model의 초기화 과정에 대한 process를 실행해야지 되는것을 의미합니다. test라는 상대경로로 옮겨왔기 때문에 경로의 depth가 달라지는 것에 주의할 필요가 있습니다. 그리고 test의 시작전에 항시 실행되어야지 되는 global before이기 때문에 before로 구성되어야지 됩니다.

'use strict';

before(function () {
  var express = require('express'),
    config = require('../config/config'),
    db = require('../app/models');
  var agent = require('supertest');

  var app = express();

  require('../config/express')(app, config);

  db.sequelize
    .sync()
    .then(function () {
      app.listen(config.port, function () {
        console.log('Express server listening on port ' + config.port);
      });
    }).catch(function (e) {
      throw new Error(e);
    });
  console.log('before');
  global.app = app;
  global.agent = agent(app);
});

mocha로 test running

이제 mocha를 이용해서 다음 command로 실행이 가능합니다.

mocha test/app.test.js test/**/*.test.js

mocha.opts 설정

좀더 편한 test 환경을 만들기 위해서 mocha.optsapp.test.js를 넣어두면 편합니다.

--timeout 5000
--full-trace
test/app.test.js

이 때, mocha.opts파일의 위치는 다음과 같이 구성합니다.

└── test
    ├── app.test.js
    ├── controllers
    ├── mocha.opts
    └── models

controller test code 작성

controller test code는 다음과 같이 구성될 수 있습니다. app.test.js에서 supertest의 request를 global.agent로 지정했기 때문에, test에서는 언제나 agent를 접근 가능해서 test code를 작성하기 좀 더 편해집니다.

'use strict';

var assert = require('assert');

describe('func01', function () {
  it('test', function () {
    console.log('abc');
  });

  it('call /', function (done) {
    agent
      .get('/')
      .expect(200)
      .end(function (err, res) {
        // console.log(res);
        done(err);
      });
  });

  it('call / - 2', function (done) {
    agent
      .get('/')
      .expect(500)
      .end(function (err, res) {
        done();
      });
  });
});

Summary

nodejs는 compile 언어가 아니기 때문에, 실행되기 전까지 정상적인 code를 작성했는지를 헛갈릴 수 있습니다. 개인적으로는 원론적으로 test를 거치지 않으면 compile 언어 역시 마찬가지로 문제가 있다고 생각합니다만. test를 잘 만들어주는 것과 test를 잘 할 수 있도록 해주는 것은 자신 뿐 아니라 다른 개발자들에게도 매우 좋은 일들입니다. 이건 정말 잘 할 필요가 있다고 생각합니다.

mocha를 이용한 test는 매우 유용합니다. 만약에 nodejs를 이용한 개발을 하고 있다면, test를 어떻게 할지에 대한 고민을 꼭 해보시길 바랍니다.

저작자 표시 동일 조건 변경 허락
신고
Posted by xyzlast Y2K

jenkins를 이용해서 nodejs project의 CI를 구성하는 방법은 다음과 같다.
기본적으로 mocha와 istanbul을 이용해서 xunit.xml과 codecoverage.xml 파일을 생성한다.

Test 결과의 저장 - xunit.xml 생성

junit의 결과값과 동일한 xml 파일을 생성한다. mocha에 다음과 같은 plugin을 설치한다.

npm install spec-xunit-file@0.0.1-3

위 plugin을 설치하면 mocha에서 xunit.xml 파일을 생성해줄 수 있다. unit test와 동시에 xunit.xml 생성을 위해서는 다음과 같은 command를 실행한다.

mocha test/**/*.test.js -R spec-xunit-file

이제 root directory에 xunit.xml 파일이 생성된 것을 볼 수 있다.

CodeCoverage 테스트 실행

istanbul을 먼저 설치한다.

npm install -g istanbul

istanbul에서 mocha를 이용한 test coverage를 해준다.

istanbul _mocha test/**/*.test.js -R node_modules/spec-xunit-file

위 command는 기본적으로 mocha를 실행시키는것이기 때문에, Code Coverage를 수행하는 경우에는 Test결과는 같이 생기게 된다. 주의점은 package dependency로 spec-xunit-file을 넣어주는 것이다. 이는 istanbul에서는 기본적으로 mocha의 기본적인 reporter만을 인식하게 되고 나머지는 직접 경로를 통해서 얻어야지 되는 단점이 있기 때문이다.

이제 생성된 결과를 보면 coverage라는 폴더가 생겨있다. 이 폴더를 지정해서 Cobertura xml을 생성해줘야지 된다. xml 생성은 다음 cmd를 통해서 생성가능하다.

istanbul report cobertura --root coverage --dir coverage

done이 나오면 cobertura-coverage.xml이 있는 것을 확인할 수 있다.

jenkins에 project의 등록

jenkins에 등록하기 위해서는 위 2가지 process를 순차적으로 실행시키면 된다. 그렇지만 문제가 두가지가 있다.

  1. spec-xunit-file를 상대경로를 사용할 수 없다. - jenkins에서 bash를 따로 실행시키는 것은 tomcat의 절대경로에 따라서 변경이 되게 된다. 따라서 tomcat의 경로를 넣어줘야지 되는 귀찮음이 생긴다.
  2. xunit.xml 파일의 절대경로를 얻어내야지 된다. - 역시 위와 같은 이유이다. spec-xunit-file의 경우 process path/xunit.xml파일로 생성되기 때문에 이에 대한 절대 경로를 얻어서 넣어줘야지 된다.

위와 같은 문제를 해결하는 npm package인 jenkins-mocha를 이용하는 것이 더 좋다고 생각된다.
jenkins-mochaistanbulmocha에 대한 wrapper기능만을 제공하지만, unit.xml파일과 coverage를 jenkins prorject folder에 생성해주는 역활을 담당하게 된다.

npm을 이용해서 설치후, package.json 파일에 다음 내용을 추가한다.

"scripts": {
    "test": "jenkins-mocha test/**/*.test.js"
},

등록 절차

freestyle project로 신규 project로 등록을 한다.

feeStyleProject

Build Process 등록

Execute Shell을 선택하고, 다음 Step들을 추가한다.

  1. npm install : npm module을 다운 받는 process
  2. npm test : istanbul, mocha를 이용해서 test를 구동하고 coverage를 계산하는 process
  3. istanbul report cobertura --root artifacts/coverage --dir artifacts/coverage : coverage.json을 cobertura.xml로 변경하는 process
Report file 등록

JUnit test report와 Code Coverage를 위한 Cobertura Coverage Report를 등록시키면 된다.
jenkins-mocha에서 생성되는 기본 report 파일들의 위치는 다음과 같다.

  1. xunit.xml : artifacts/xunit.xml
  2. Cobertura xml report: artifacts/coverage/cobertura-coverage.xml

위 두 파일을 Post Build Process에 다음과 같이 등록하면 된다.

Completed

이제 build를 하면 다음과 같은 결과를 볼 수 있다.

nodejs application은 test가 일반 java application보다 빠르게 끝나는 것이 특징이고, 무엇보다 TDD나 BDD를 사용하기가 좀 더 용의한 언어구조를 가지고 있다. 좀 더 즐거운 개발을 할 수 있지 않을까 생각된다.

저작자 표시 동일 조건 변경 허락
신고
Posted by xyzlast Y2K
TAG CI, node, test


티스토리 툴바