Express에서 CORS 허용하기

BS가 SPA(Single Page Application)을 하나 만들고 있습니다.
SPA는 BS의 핸드폰에서 실행할 것이고, 서버는 node.js + express 를 사용합니다.
보안상의 이유로 일반적인 환경에서는 도메인을 넘어서는 요청이 처리 되지 않습니다.
node.js + express 로 어떤 기능을 제공하는 RESTful API를 구현한다면 CORS를 허용해야 합니다.

일단 용어 정리부터…

  • SPA(Single Page Application)
    쉽게 설명하자면…
    index.html 파일 하나로 동작하는 웹어플리케이션을 말합니다.
    BS는 Cordova를 사용해서 SPA 모바일 앱을 하나 만들고 있습니다.
  • CORS(Cross Origin Resource Sharing)
    보안상의 이유로 웹페이지가 수행되고 있는 도메인이 아닌 리소스를 요청하는 것을 제한합니다.
    a.com에서 실행되는 어플리케이션이 b.com의 리소스를 사용할 때, 이러한 보안이 없다면 b.com에 의해 a.com이 망가질 수 있기 때문입니다.
    하지만 RESTful API를 구현한다면 API를 제공하는 서버와 어플리케이션이 수행되는 서버가 동일한 도메인을 사용하라는 보장이 없으므로 서버는 CORS를 허용하도록 작성되어야 합니다.
    물론 CORS를 허용하기에 다른 보안 방법을 추가해야 합니다. 예를 들면 API를 접근하는데 필요한 인증 시스템이나, 네트워크 수준의 접근 제한 등…

본론으로 들어가서…

BS처럼 node.js + express 를 사용해서 API 서버를 구현한다면 CORS 관련 미들웨어를 하나 추가하는 것으로 쉽게 처리할 수 있습니다.
물론 미들웨어 없이 “Access-Control-Allow-Origin”, “Access-Control-Allow-Headers”와 같은 헤더를 직접 명시하는 방법도 있겠지만…
BS는 훌륭한 미들웨어가 있다면 사용하는 것이 옳다고 생각합니다. (자동차를 만들기 위해 바퀴부터 개발하는 것은 아니라고 생각하기에…)

Express CORS 미들웨어: https://github.com/expressjs/cors

설치

npm install cors

초간단 사용

const express = require('express');
const cors = require('cors');
const app = express();

app.use(cors());

app.get('/products/:id', function (req, res, next) {
  res.json({msg: 'This is CORS-enabled for all origins!'});
});

app.listen(80, function () {
  console.log('CORS-enabled web server listening on port 80');
});

Node.js에서 cluster 사용시 디버깅

v0.10.36 이 버전에서 cluster 모듈을 사용한 어플리케이션을 –debug 옵션으로 실행하면 fork() 된 worker 들도 같은 debug port를 사용해서 오류가 발생합니다. 그래서 디버깅을 위해 몇가지 작업을 했었는데…


cluster.setupMaster()를 통해 fork 될 worker에게 전달하는 execArgv 값을 master의 process.execArgv 에다가
–debug-port=XXXX 이런 식으로 바꾸어서 실행 되도록 하는 등… 번거로운 작업을 했습니다.


BS의 경우에는 아예 모듈하나 만들어서 debug 모드에서는 cluster를 사용하지 않고 단일 프로세스로, debug 모드가 아닐 경우에는 cluster를 사용하는 멀티 프로세스로 동작하도록 했습니다.


그런데 v0.12.0 에서 이 부분이 변경이 되어 발표되었습니다.


[GitHub의 Node.js cluster.js 소스]를 보면


var workerEnv = util._extend({}, process.env);
var execArgv = cluster.settings.execArgv.slice();
var debugPort = process.debugPort + id;
var hasDebugArg = false;

workerEnv = util._extend(workerEnv, env);
workerEnv.NODE_UNIQUE_ID = ” + id;

for (var i = 0; i < execArgv.length; i++) {
  var match = execArgv[i].match(/^(–debug|–debug-brk)(=\d+)?$/);

  if (match) {
 execArgv[i] = match[1] + ‘=’ + debugPort;
 hasDebugArg = true;
  }
}

if (!hasDebugArg)
  execArgv = [‘–debug-port=’ + debugPort].concat(execArgv);


위와 같은 코드를 볼 수 있습니다.
worker 프로세스를 생성하는 함수 createWorkerProcess() 안에 있는데
내용은


master의 debug port + worker의 id 한 값을 debug port로 무조건 사용한다

입니다. 심지어는 debug 모드가 아닌 경우에도 –debug-port 옵션을 설정합니다!


이것 때문에 debug 모드를 검사하던 방법을 바꿔야 했습니다.


var isDebuggingMode = process[“execArgv”].some((argv) => {
  return /^(–debug|–debug-brk)(=\d+)?$/.test(argv);
});

그런데 여기서 드는 의문점…


TCP/IP 의 port 라는 것은 0 ~ 65535 일 수 밖에 없는데 (2바이트의 정수)…
cluster.fork()로 생성되는 worker의 id 는 무조건 증가만 되게 되어 있어서


cluster.fork = function(env) {
  cluster.setupMaster();
  var id = ++ids;
  var workerProcess = createWorkerProcess(id, env);
  var worker = new Worker({
    id: id,
    process: workerProcess
  });
  …

[Node.js cluster 문서의 cluster event “exit” 설명]에 나오는 아래 예제처럼 작성할 경우


cluster.on(‘exit’, function(worker, code, signal) {
  console.log(‘worker %d died (%s). restarting…’,
    worker.process.pid, signal || code);
  cluster.fork();
});

결국은 debug port가 65535가 넘어가는 사건이 발생하게 됩니다.


결론




  1. Node.js의 cluster 모듈 사용할 때에는 worker를 동적으로 계속 생성하지 않도록 한다.

  2. 동적으로 fork()할 일이 있는 어플리케이션은 cluster 모듈을 사용하지 말고 child_process 모듈을 사용한다.

  3. port 공유가 필요하고 동적으로 fork()를 해야 하는 어플리케이션은 C++로 직접 구현해서 붙인다
    (또는 잘 찾으면 이런 걸 한방에 해결하는 module이 있을지도)

 

Node.js에서 console 출력이 표시되기 전에 프로그램이 종료됩니다.

아래와 같은 코드를 실행해 보았습니다.
실행 환경은 Windows 에서 Nodeclipse 플러그인 설치한 Eclipse입니다.

(function() {
    var i;
    for (i = 0; i < 100; ++i) {
        console.log(i);
    }
    process.exit(0);
}());

당연히 마지막에 99가 찍히리라… 생각되겠지만
콘솔에는 0 하나 달랑 출력되고 프로그램은 종료되었습니다.
현상의 원인은 console 출력이 완료되기 전에 process.ext()에 의해서 프로그램이 종료되었기 때문이죠.
이걸 출력하기 위해 setTimeout()을 사용하는 건 좀 이상하고…
그래서 아래와 같은 방법으로 시도를 했습니다.

(function() {
    var i;
    for (i = 0; i < 100; ++i) {
        console.log(i);
    }
    process.stdout.write("", function() {
        process.exit(0);
    });
}());

원하는대로 출력되었습니다.
그런데 이런 증상은 Windows에서만 발생합니다.
첫번째 소스로도 Linux 에서는 원하는 동작을 합니다.
두번째 소스도 마찬가지 입니다.
그 이유를 [Node.js 공식페이지]에서는 아래와 같이 설명합니다.


process.stderr과 process.stdout은 다른 스트림과 달리 보통 blocking으로 동작합니다.

  • 보통의 파일 또는 TTY에 출력할 경우 blocking

  • pipe로 사용할 경우에는

    • Linux/Unix에서는 blocking

    • Windows에서는 non-blocking

그렇군요… 그래서 console의 결과를 pipe를 통해 IDE로 가져오게 되면 Windows에서 non-blocking으로 동작하는 것입니다.