기본 콘텐츠로 건너뛰기

REST없이 DDP로 외부 Meteor 서버 데이터 끌어오기

Meteor는 Web Application이지만 기존 HTTP 통신을 최초에 자원을 읽어올때 한번만 사용하고 그 이후로는 DDP(Distributed Data Protocol https://www.meteor.com/ddp)를 이용하여 자료교환을 합니다.

Meteor 작동방식(http://meteorhacks.com)

DDP를 Websocket을 위한 REST라고 이야기하기엔 결정적으로 다른 차이점 하나가 있다.
HTTP의 경우는 요청(Request)을 하면 응답(Response)를 주는 방식으로 연결이 유지가 되지 않는 반면, DDP 는 기본적으로 Connection을 한번 맺으면 클라이언트와 계속 연결을 유지하면서 실시간 송수신을 주고 받는다.
DDP는 WebSocket을 사용하고 SockJS를 사용하여 XMLHttpRequest 로 대체하기도 한다.
어떤 식으로 전송/수신을 하는지 Chrome의 Developer Tools를 통해 WebSockets 흐름을 한번 살펴보자.

DDP Websocket 캡쳐화면

Meteor에서 클라이언트/서버간 통신하는 내용을 살펴보면 의외로 별게 없는데 Random.id()로 생성한 id값과 msg 종류. name 이름과 나머지 인자값들이 전부다.

클라이언트쪽에서 보면 
1. 연결 요청(connect)한다.
{
  "msg":"connect",
  "version":"pre2",
  "support": ["pre2","pre1"]
}

2. subscribe 한다.
{
  "msg":"sub",
  "id":"j9jDEAyCxPnNELqGE", /* 이 id로 ready를 받는다 */
  "name":"meteor.loginServiceConfiguration",
  "params":[],
  "route":null
}

3. unsubscribe 한다.
{
  "msg":"unsub",
  "id":"2CJPyYjDkF6cBBjPa" /* subscribe 했던 ID */
}

4. method 실행한다.
{
  "msg":"method",
  "method":"login", /* login Method 의 예 */
  "params":[{
    "resume":"1LD6dzePGgaVXWanStM_t0s1VzzwlCjNpWsnV4uwIHw"
  }],
  "id":"1"
}

이게 전부.
여기에 대응하는 서버쪽의 응답은
1. 연결 확인(connected)
{
  "msg":"connected",
  "session":"EndYfXuGFeqmK6cTR"
}
2. publish에서 해당 collection을 added 후 ready 반환
{
  "msg":"added",
  "collection":"meteor_accounts_loginServiceConfiguration",
  "id":"DPEKK2xYQ7WHJsBA3",
  "fields":{
    "service":"twitter",
    "consumerKey":"......."
  }
}
....
{
  "msg":"ready",
  "subs":["j9jDEAyCxPnNELqGE"] /* subscribe시 요청했던 id */
}

3. unsubscribe의 결과인 nosub을 반환
{
  "msg":"nosub",
  "id":"2CJPyYjDkF6cBBjPa" /* unsubscribe 요청한 id */
}

4. method 결과인 result 를 반환
{
  "msg":"result",
  "id":"1", /* method 요청 했던 id */
  "result":  {
    "id":"aKcCLrJJHrzNHbJSs",
    "token":"1LD6dzePGgaVXWanStM_t0s1VzzwlCjNpWsnV4uwIHw",
    "tokenExpires": {
      "$date":1447776670432
    }
  }
}

요렇게 쌍으로 이루어져있다.
눈으로 읽을 수 있는 JSON구조라서 파악이 어렵지 않다.

이 DDP의 구조를 파악하고 있다면 클라이언트건 서버쪽이든 Meteor Platform과 통신할 수 있는 코드를 만들 수 있다는 것이다.

"Meteor 의 Server / Client 간 isolation 이 가능하냐 나는 완전한 Meteor를 원한다!!"라고 AngularJS Meetup의 Shawn McKay(http://www.shmck.com/)님이 물어보셔서 "DDP를 하면 된다! DDP가 만세다!"라고 맥주집에서 고래고래 소리를 질렀었는데 최근 글들을 보니 뭐 지금쯤은 동의하실 것 같다ㅋ

언어별로 DDP 클라이언트(http://meteorpedia.com/read/DDP_Clients)들이 참 다양하게 구현이 되어있으니 쓰기만 하면 된다.

이론은 알았으니 실제로 외부 Meteor 사이트를 데이터베이스 연결없이 가지고 오는 예를 한번 구현해보자.

(전체 소스 구현 및 예제) http://meteorpad.com/pad/N5ABJRpjd4id3Pf4s/remoteConnection
오직 클라이언트쪽 구현만으로 시도해 볼 것이므로 서버는 만질 필요가 없다.

먼저 DDP로 외부 URL 연결을 시도할 객체를 생성하고

remote = new DDP.connect('http://www.meteorjs.kr');

중요한 것은 일단 Collection을 어디와 연결하느냐인데 일단 두가지 방법이 있다.
직접적으론 collection 선언시 인자로 DDP.connect한 핸들러를 넘겨주는 방법이 있고
Posts = new Meteor.Collection('posts', remote);

아예 Meteor.connection 객체를 외부 DDP로 바꿔치기 하는 방법이 있다.
Meteor.connection = remote;
Posts = new Meteor.Collection('posts');

이 경우 편리한 점은 Meteor.loginWithPassword 같은 내장 Method들을 그대로 쓰면 된다는 장점이 있다.

만일, 여러개의 DDP를 원격으로 제어하고자 한다면 Websocket의 내용을 보고 참조해서 넣으면 된다. 로그인의 경우는 이렇다.
{
  "msg":"method",
  "method":"login",
  "params":[{
    "user":{
      "username":"spectrum"
    },
    "password": {
      "digest":"xxxxxxxxxxxxxxxxxxxxx",
      "algorithm":"sha-256"
    }
  }],
  "id":"1"
}
여기에서 method 이름과 params를 아래와 같이 호출하면 된다.
Meteor.call('login', { user: { username: "spectrum" }..... )

그러면 예상하는 결과로
{
  "msg":"result",
  "id":"1",
  "result": {
    "id":"aKcCLrJJHrzNHbJSs",
    "token":"xxxxxxxxxxxxx",
    "tokenExpires":{
      "$date":1448039686373
    }
  }
}

이런 형태의 token 값을 받을 것이다.
이 토큰 값을 가지고 저장하고 있다가 다음 로그인에 사용하거나 하면 된다.
예전 SRP login에 대한 포스트(http://spectrumdig.blogspot.kr/2013/08/meteor-application-ddp-2-srp.html)를 쓴 적이 있는데 2년 전에 비해 좀 깔끔해졌다.

마지막으로 subscribe인데, 위에서 가져올 빈 컬렉션을 먼저 준비해두었다면 사용할 Template 의 onCreated 에서 this.subscribe 대신 remote = new DDP.connect('http://www.meteorjs.kr'); 에서 받은 객체를 remote.subscribe 식으로 사용하면 된다.

Meteor.connection을 바꿔치기 했다면 Meteor.connection.subscribe("....", ...) 형식으로 사용한다.
DDP를 이기종/플랫폼간 실시간 메시지 큐로도 사용할 수 있을 것이고 0MQ 같은 것들이랑 연동한다던지 외부 서버와의 DB 독립적으로 자료교환하는 등의 응용도 가능할 것이다.

단, OAuth를 사용하는 경우는 약간 고민이 필요할 것 같다. 대부분의 경우(facebook/twitter등) OAuth 인증 후 callback URL을 요구하는데 remote DDP 연결을 통하면 URL이 달라서 다소 어려움이 있겠다.

개인적으로는 도메인과 상관없는 Single Sign On 로그인 서버를 Meteor 로 만드는 것도 꽤 멋진 일이라고 생각한다.
웹이든 앱이든 서버 배치든 뭐든지 DDP로 연결해보자!
DDP는 간결하고 잘 작동하며 아름다운 프로토콜이다!

이 블로그의 인기 게시물

meteor로 nw.js 개발하기.

실제로 nw.js 어플리케이션을 개발하다보면 UI구현하기 막막하고 수동으로 리프레쉬 하는 것도 귀찮아서 Meteor 연동을 하려고 했더니 생각보다 간단했다.

디렉토리 구조는 먼저 이렇게 잡았다.
`- app
  `-client
  `-public
`- dist
  `- 배포용 html,css,js
  `- package.json
`- package.json 아이디어는 이렇다. nw.js의 시작페이지를 http://localhost:3000으로 두고 배포시엔 meteor client 배포툴인 meteor-build-client를 사용하여 html,css,js 로 분리하는 계획이다.

가장 중요한 nw.js 용 package.json 파일은 아래와 같이 구성한다.
{
  "main": "http://localhost:3000",
  "node-remote": "http://localhost:3000",
  "name": "<앱이름>"
} 이게 전부. 어떻게 보면 Web과 nw.js를 동시에 개발할 수도 있는 환경이기도 한 것이다.
meteor create app 을 해서 meteor용 앱을 만들고 meteor 를 시작한다.
그리고, 위의 package.json이 있는 경로로 돌아가서 nw . 으로 nwjs를 실행한다.

한번 번쩍하더니 잘 된다.
cordova 등에서 index.html 대신 http://localhost:3000을 하는 것도 비슷한 느낌이다.

즐겁게 개발을 일단 마구 하고 실제로 배포하기 위해서는 개발환경이 아니라 html,css,js로 구성된 배포본을 만들어야한다.
npm install -g 해도 되지만 어짜피 Meteor에서만 쓸거
meteor npm install -g meteor-build-client 해버릴거다.

개발은 문제 없어 보이고 배포판을 한번 만들어보자. meteor app 이 있는 경로(meteor run으로 …

RxJS - ReactiveX 인터뷰

A: 왜 RxJS입니까
B: javascript는 참 쉽고 친숙한 언어죠.
A: 별로 그렇게 생각 안합니다만.
B: 그래서 좀 어렵고 있어보이는게 뭘까 싶어서...
A: 네?
B: 함수형이라는게 유행하기도 하고
f(x) 좋쟎습니까? 미스테리~ 미스테리~ 정수정짱짱 으아아

이런 수학선생님이라면 수포자 따윈 A: ...
B: 그리고 반응형이라는 말 뭔가
A: 뭔가?
B: 대충대충해도 막 알아서 할거 같고...
A: 그럴리가요?
B: 안그렇겠죠?
A: 네
B: 네

(잠시만 기다려주세요)

A: 그래도 뭔가 매력이 있으니 이렇게 시간을 내셔서 이것저것 Rx에 대해 글도 쓰고 이야기도 하고 그러시는거 아닌가요?
B: 매력이라.
으음.
제가 팔꿈치 터널 증후근이 좀 있어요.
오른손 세끼손가락, 약지손가락이 저립니다.
A: 무슨 상관이?
B: 그래서 각종 괄호를 쓰는게 너무 힘듭니다.
소중대괄호 만든 사람 죽었으면.
Hello world (ASCII): https://esolangs.org/wiki/Parenthesis_Hell
A: 이미 옛날에 돌아가셨겠죠.
B: 그렇겠네요.
아무튼 그래서 소중대괄호 의존이 적은 커피스크립트를 쓰는데요.
A: 빨리 본론을 말씀해주시죠.
B: 커피스크립트에서 가로로 80자 이상쓰면 Line exceeds maximum allowed length 라고 경고해요.
A: 그래서요?
B: 근데 Rx를 쓰면 코드를 가늘게 쓸 수가 있더라구요.
A: 호오?
B: 그리고 = 쓰는 것도 너무 힘듭니다.
A: 네?
B: 오른손을 쓰쟎아요.
A: ...
그러니까 정리하면
1. 괄호가 힘들다
2. 커피를 쓴다
3. 커피는 길게 쓰면 경고
4. Rx를 쓰면 코드가 가늘다
5. 대입문을 줄이고 싶다.
B: 네
하지만 5번은 생각보다 별로...
A:
B:

B: 아!
A: ?
B: 코드가 가늘어서 좋은 점이.
A: 네.
B: 핸드폰에서 코드를 보기 좋습니다.
A: ... 왜 팔꿈치 터널 증후근이 안 낫는지 알겠습니다.
B: 도와주세요.
쇠고기 사묵으면 나을 것 같습니…

Troubleshooting - Meteor package가 적용이 되지 않을 때

버전 1.5 기준 package.js에서 Package.onUse 에 새 패키지를 추가했는데 인식하지 못하는 경우가 있다.
Package.onUse((api) => {
  api.use([
    'vulcan:core',
    'vulcan:forms',
    'vulcan:accounts' /* <-- 추가함! */
  ]);
...
} 내부패키지건 원격패키지건 안되는 안된다. 이럴 때 meteor add 후 meteor remove 해도 되지만 더 간단한 방법이 있다. meteor update vulcan:accounts 이렇게 update 해주는 방법이 있다. .meteor/package 파일을 건들지 않아서 좋다. 그래도 역시 좋지 않다. Meteor 스럽지 않다. https://github.com/meteor/meteor/issues/7721 현재 1.5.2에서도 해결이 안되었군요. 해결되어 적용되면 다시 글 올리겠습니다.