기본 콘텐츠로 건너뛰기

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는 간결하고 잘 작동하며 아름다운 프로토콜이다!

이 블로그의 인기 게시물

Mac 주의 - 약간 빡치는 git 대소문자 변환 파일명 변환(renaming)하는 법

Mac에서 종종 화나는 일 중 하나가 파일 시스템이 대소문자를 구분하지 않는 점이다.
가령, A.png를 a.png로 바꿔야할 경우 HFS 기준으론 전혀 바뀐 점이 없는 걸로 인식하는데 다소 화가난다.
하지만, 방법이 없는 것은 아닌데

A.png를 임의의 다른 파일로 바꾼다. (ex. A_.png)1에서 바꾼 상태에서 staging 한다. (git add A_.png)1의 파일을 다시 원래 바꾸려고 했던 파일로 바꾼다. (mv A_.png a.png)3을 다시 staging 한다. (git add a.png)commit 한다.https://stackoverflow.com/a/40307511/880991 에서 답변해준 Chris Barr 님에게 감사를.
* 추가

실제로 해보니 git mv --force A.png a.png 가 더 간단하다. 이는 바로 staging 영역에 add까지 해준다.

graphql 연습 /w launchpad

https://launchpad.graphql.com/mw9wkzv99
단순 전체쿼리+조건쿼리+추가

http://graphql.org/graphql-js/passing-arguments/
참고. random ID는 crypto 1.0.1 사용
  type Query {
    Members: [member]
    getMember(id: ID!): member
  }
  type member {
    id: ID!
    text: String
  }
  input memberInput {
    text: String
  }
  type Mutation {
    addMember(member: memberInput): member
  } SQL 정의. facebook 쪽은 스트링에 지지는 거 진짜 좋아하네. *.gql 파일이 있다고 하니 이해해주자.
resolver는 var buffer = [];
const resolvers = {
  Query: {
    Members: (root, args, context) => {
      return buffer;
    },
    getMember: (id)=> {
      return buffer.find(o=>o.id)
    }
  },
  Mutation: {
    addMember(_, {member}) {
      const mm = { ...member, id:randomBytes(10).toString('hex') };
      buffer.push(mm);
      return mm;
    }
  }
}; 평범 평범.
https://dev-blog.apollodata.com/tutorial-graphql-subscriptions-server-side-e51c32dc2951 다음으로 pub/sub 연습.
https://launchpad.graphql.com/xvn94n3ql   type Subscription {
    memberAdded: member
  } member가 added되는 순간을 감시. imp…

즐거운 Online Prototyping Tool 들

jsbin, codepen, jsfiddle 이런 것들은 일단 생략. 너무 유명한 것들이라.

https://launchpad.graphql.com - node.js 기반 graphQL 연습장. 이것만으로도 충분히 훌륭한 백엔드
https://codesandbox.io/ npm 사용이 가능한 클라이언트 사이드 연습장. webpackbin이 너무 문제가 많아서 찾아본 것.

https://scrimba.com 이건 codesandbox+ asciinema(https://asciinema.org/) 같은 느낌인데 키 녹화와 음성 녹화 기능이 추가되었다. 다 좋은데 화살표 키로 빨리감기 뒤로감기 기능이 안되고 익스포트(youtube등)으로 지원이 없는게 아쉽다.

이 둘이 만나면? https://codesandbox.io/s/jvlrl98xw3?from-embed
뭐야 이거 무서워 하지마 ㄷㄷ;  graphql+react-native-web(부왘ㅋㅋ)

https://repl.it/languages 전통을 자랑하는 REPL 도구. 지원 언어 종류가 -_-;;;;;

https://tio.run/# repl.it? 장난함? 얘는 지원 언어가 무려 386종류. J랑 아희도 있다.

https://play.golang.org/ 즐거운 go playground. 소스 포멧팅 넘 좋아.

http://decaffeinate-project.org/repl/ 최고의 coffeescript REPL. 원래 용도는 coffee를 ecma6코드로 바꾸는 것이지만...

https://scaphold.io
https://www.graph.cool/ graphql backend service. scaphold.io는 설치도 필요없는 클라우드. graphcool은 호스팅+클라우드 다있음. 둘 다 막상막하. 푸쉬서버도 되고 뭐 미친득.

https://glitch.com/ gomix에서 결국 glitch로 안착.  node.js

https://www.shadertoy.com 잘하고 싶다! 쉐이다! 오디오도 된다!

http:/…