기본 콘텐츠로 건너뛰기

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

이 블로그의 인기 게시물

Unicode 2.0 에서 한글의 이해

요즘 SNS이나 SNG등등 기계적으로 문장을 생성하는 프로그램들이 넘쳐나는 시대에 의외로 한글처리를 제대로는 프로그램들이 드물구나 하는 생각에 간단한 한글 자소 분석기를 만들어보았다.
링크는 이쪽(http://jsbin.com/ofoqal/10/edit)

애초에 만든 목적은 다음과 같다.
조사처리(은/는, 이/가, 을/를 등등)를 위해 단어의 마지막 글자의 종성을 조사하기 위함인데 예문을 들어보자면

"준기 강남에서 사진 찍었다."
"예슬 홍대에서 식사 했다."
"슬기 대화방에서 나갔습니다."

"준기님은 강남에서 사진님을 찍으셨습니다 고갱님" 이라고 말하면 할말 없다.

한국식 소프트웨어(꼭 소프트웨어가 아니더라도)의 특징이자 장점이 무엇이냐라고 물으면
귀찮을 정도로 깨알같은 디테일이라고 대답할텐데 한글 기계화 작업에 대한 중요성은 프로그램을 만드는 사람들에게도 별로 중요하게 다가오지 않나보다.

에또 사설이 길었다.
한때 우리는 한글코드체계의 비표준 숲속에서 너무도 괴로운 나날들을 보낸 역사가 있다.
KSC5601부터 시작해서 Microsoft통합형한글을 지나 Unicode 2.0의 시대가 왔다.

개인적으로 UTF-8을 사용하지 않고 EUC-KR이나 CP949를 쓰는 제품이나 서비스의 업체의 대표/관계자에게 1억 미만의 벌금 혹은 3년 이하의 금고형의 실형을 내려줬으면 할 정도로 너무나 많은 사람들을 불행하게 하고 막대한 비용을 지출한 악의 근원이라고 생각한다.

하지만 광명이 왔다.
기계적으로 납득이 가능한 검색 및 정렬이 용이한 Unicode 의 시대가 열렸단 말이다.
지금 당신이 복사해서 붙이고 있는 팁들 보다 훨씬 쉽고 명쾌하니 다음 그림을 한번 보자.
(출처:http://www.w3c.or.kr/i18n/hangul-i18n/ko-code.html) 어떤가?
무쟈게 쉽지 않은가?

현대 한글은 초성 19자, 중성 21자, 종성 27자로. 총 19 x 21 x 28 (종성없음 포함…

즐거운 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:/…

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…