2012년 12월 13일 목요일

Android/iOS mobile web debugging

Google Chrome 이나 Firefox, Safari 그리고 아쉽지만 그런대로 Internet Explorer까지 모두 개발자 도구를 지원하여 DOM 구조를 보거나 요청별 응답시간을 추적하거나 CSS를 살펴보거나 할 수 있다.
이건 너무 편하고 당연해서 우리가 모바일 환경(Android/iOS)에서 같은 환경을 사용할 수 없는게 모바일 웹 개발의 어려운 점이기도 한데.
역시 찾아보니 방법이 있다.

먼저 iOS 같은 경우는 설정 > Safari > 고급 > 웹속성 을 켜주면
iOS장비와 Safari가 깔린 컴퓨터를 서로 연결하여 개발자용 메뉴 > (해당 기기 이름) 을 통해 접근가능하다.
물론 컴퓨터용 Safari에서 미리 환경>고급>메뉴 막대에서 개발자용 메뉴 보기 를 체크해줘야한다.

그리고 Android의 경우 Chrome for Android 가 있다면 좀 더 좋은 환경을 제공한다.
https://developers.google.com/chrome/mobile/docs/debugging 내용 참조.
USB 연결 후 adb devices 에서 장비 연결을 확인하고
Chrome for Anroid 에서 Settings > Advanced > Developer tools 로 들어가 Enable USB Web debugging 를 체크
그다음 adb forward tcp:9222 localabstract:chrome_devtools_remote 를 실행해서 9222 포트를 연결하고
크롬에서 localhost:9222를 띄워서 디버깅할 창을 선택하면 된다.
https://developers.google.com/chrome-developer-tools/docs/timeline 도 같이 쓸 수 있따.

센스가 있는 분들이라면 iOS건 Android건 에뮬/시뮬을 통해서도 할 수 있지 않을까라고 생각할 수도 있을텐데
맞다. iOS Simulator나 AndroVM(http://androvm.org/blog/download/) 같은 걸 사용해도 무방하다.
USB를 장비랑 주렁주렁다는 연결하는 것보다 능률도 높고 간편하므로 이쪽을 추천한다.

2012년 11월 23일 금요일

Meteor에서 file API를 사용한 upload 방법

https://gist.github.com/4132544
코드는 이쪽에.

Meteor.saveFile = (file)->
  fileReader = new FileReader()
  fileReader.onload = (blob) ->
    Meteor.call 'saveFile',
      file:file
      blob:blob.srcElement.result
  fileReader.readAsBinaryString file
  return
# event for upload
_.extend Tempalte.something,
  events:
    'change input[name=attach]': (e)->
      _.each e.srcElement.files, (file) ->
        Meteor.saveFile file

클라이언트에서 FileReader 객체를 생성하고 onload 시 파일속성과 바이너리를 인자로 Call을 해준다. submit을 타지 않고 Call을 사용하여 직접 전송한다는 점에 주목.

app=__meteor_bootstrap__.app
  fs=__meteor_bootstrap__.require 'fs'
  app.stack.unshift
    route: '',
    handle: (req, res, next)->
      Fiber(->
        unless /upload\/\/*/gi.test req.url
          next()
        else
          res.write fs.readFileSync("#{process.cwd()}/server/tests/upload/images/#{req.url.replace('/upload/','')}")
          res.end()
      ).run()
  Meteor.methods
    saveFile: (param)->
      if param.file.type.indexOf('image')+1
        Fiber(->
          fs.writeFileSync "#{process.cwd()}/server/tests/upload/images/#{param.file.name}",param.blob,'binary'
        ).run()

Meteor.methods부분 : 서버쪽에서 saveFile이란 method를 받으면서
file이름과 바이너리를 받아 파일 쓰기를 한다.
이때 경로는 tests 경로 아래로 두는데 프로젝트 디렉토리가 아닌 곳으로 해줘도 상관없지만
프로젝트 디렉토리 아래 업로드할 파일을 적재할 경우
Meteor가 파일 생성 여부를 스캔하여 어플리케이션을 재시작하는 것을 방지하기 위해
예외 디렉토리인 tests 를 사용한다.

이렇게 저장한 업로드 파일은 /public 디렉토리 아래에 있지 않아 직접 링크를 전달할 수 없다.

Routing 부분 : __meteor_bootstrap__.app 객체로부터 stack에 request 처리를 직접해준다.
route 를 '' 로 할 경우 모든 요청에 대한 처리를 할 수 있으므로 이를 사용한다.
그러면 handle에서 request별 처리를 할 수 있는데
우리는 /upload/xxxxx 형태의 요청에 대해서만 처리하고 나머지는 next()를 사용해 포워딩해주기로 한다.
해당 형식의 URL 요청을 받을 경우 methods 에서 저장한 디렉토리 경로를 붙여서 response 에 write 해준다.

서버 사이드에서 파일을 읽거나 쓸때 병목을 방지하기 위해서 깨알 Fiber를 붙여줌.
readFileSync 나 writeFileSync 대신 readFile / writeFile 을 써서 callback으로 처리해도 되지만 예외처리 등등 귀찮기도 하고 meteor 디자인 가이드에서 CPS 스타일을 참아달라는 부탁이 있어서 Fiber + Sync 로 구현.

이런 건 뭐 메뉴얼에도 없고 검색도 안되니 소스를 뒤져보는 수밖에.
누군가 보고 '이렇게 하는게 더 나아요'라고 제시해줬으면 싶지만 현재로는 이게 제일 맘에 든다.

2012년 11월 21일 수요일

play.node() 노드 컨퍼런스 코리아 종료.

우여곡절은 있었지만 발표도 잘 했고 재밌는 것들도 많이 들었고 나름 의미 있는 것들을 몇몇 건졌다고 생각한다.
Isaac(node.js 원년멤버.풀타임 node.js 개발자이지 npm을 만든 장본인)은 생각보다 아니 생각대로 싱거운 사람이었다.
몇몇 질답이 오갔는데 다 그가 알고 있거나 우리들이 아는 뻔한 이야기의 범주를 벗어나지 못했달까.
밀도의 차이는 있지만 우리랑 별로 다르지 않구나라는 안도감도 약간.

매끄럽지 못한 부분도 있었지만 1회 치곤 괜찮았고 네이버 행사장도 꽤 좋았음. 앞으로도 계속 이런 행사 유지했으면 한다.
근데 솔까 스피커/오거나이저한테 뭔가 좀 더 현실적인 보상이 있었으면
자봉문화(?)가 못마땅하달까. 뭐만 하면 다 자봉이야. 이런 거 애들이 배워서 나중에 "우리도 열정을 가지고 공짜로 했으니 너희들도 당연히 그래야지"라고 말하는 꼰대가 될까봐 후덜덜.
스폰서들이 쓰는 비용 전체에 비하면 적은 투자인데 반해 그들은 열성적인 홍보자라서 고효율 아닌가?
컨퍼런스를 무료로 하는 것이 컨퍼런스의 질을 떨어뜨린다고 생각한다면 스텝에 대해서도 마찬가지라고 생각.

오전 세션을 사정상 참여않아 못봤는데 해외 스피커들에게 영어로 질문하는 사람들이 있었다고 한다.
당연히 이런 거 사전에 미리 제재하고 명확한 의미 전달을 위해 한국어로 질문하게 하고 전문 분야의 지식과 한국어의 의미를 잘 파악하는 전문 번역자가 통역하게 해야한다.

그나저나 우리도 뭔가 언어나 프레임워크 같은 거 만들어서 전문 통역자(당연히 현지에서 준비해주는)끼고 한국말로 연설하는 전세계 투어 같은 거 하면 정말 재밌겠네.

마무리로 Isaac 하고 찍은 사진이랑 인증 샷들 몇개 투척.







2012년 11월 17일 토요일

Unicode 2.0 에서 한글의 이해

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

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

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

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

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

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

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

하지만 광명이 왔다.
기계적으로 납득이 가능한 검색 및 정렬이 용이한 Unicode 의 시대가 열렸단 말이다.
지금 당신이 복사해서 붙이고 있는 팁들 보다 훨씬 쉽고 명쾌하니 다음 그림을 한번 보자.
어떤가?
무쟈게 쉽지 않은가?

현대 한글은 초성 19자, 중성 21자, 종성 27자로. 총 19 x 21 x 28 (종성없음 포함) = 11172자 되겠습니다.
빈값이 없이 빽빽하게 들어찼단 말이다.

초성은 ㄱㄲㄴㄷㄸㄹㅁㅂㅃㅅㅆㅇㅈㅉㅊㅋㅌㅍㅎ (19자)
중성은 ㅏㅐㅑㅒㅓㅔㅕㅖㅗㅘㅙㅚㅛㅜㅝㅞㅟㅠㅡㅢㅣ(21자)
종성은 (없음)ㄱㄲㄳㄴㄵㄶㄷㄹㄺㄻㄼㄽㄾㄿㅀㅁㅂㅄㅅㅆㅇㅈㅊㅋㅌㅍㅎ(28자)

이것 밖에 없다.
교ㅑ
  ㄱ
이런거 안된단 말이다.

자 지금 당장 javascript console을 열고 이렇게 쳐보자.
저 대로라면 한글의 가장 첫번째 글자는 무엇일까?
당연 '가' 되겠다.

> '가'.charCodeAt().toString(16).toUpperCase()
"AC00"

그렇다. 이게 시작이다. 0xAC00. 10진수로 44032.
그럼 '문' 이건 어떻게 될까?
초성인 'ㅁ'이 6번째
중성인 'ㅜ'이 13번째
종성인 'ㄴ'이 4번째 되시겠다.
44032+6*21*28+13*28+4 = 47928 인거다.

> String.fromCharCode('가'.charCodeAt() + 6*21*28 + 13*28 + 4 )
"문"

아름답다. 아름다와.
같은 방법으로 초중종을 분리해보자.

47928(문)-44032(가)=3896 이니
3896 을 기준으로

초성 Math.floor(3896/21/28) = 6(ㅁ)
중성 Math.floor((3896 - 6*21*28)/28)=Math.floor(3896/28-6*21) = 13(ㅜ)
종성 3896 % 28 = 4(ㄴ)

이게 전부다. 코드도 그냥 이것대로 한거고.
애초에 초반에 말했던 을/를, 이/가 등의 조사를 구분하는 함수를 만든다면 어떨까?

var hasJongsung = function(word) {
  return !!(word && word[word.length -1].charCodeAt()>=0xAC00 && word[word.length -1].charCodeAt()<=0xD7A3 && (word[word.length -1].charCodeAt()-0xAC00)%28);
};

그저 이렇게 한 줄 코드로 해결할 수 있다.
종성이 있을 경우 true를
종성이 없거나 한글이 아닐 경우 false를 반환한다.

지금 만들고 있는 프로그램에도 한번 적용해보자. 고기덕후세종대왕느님들이 고생해서 만든 글인데 아깝지 않은가?

2012년 11월 2일 금요일

meteorite package에 d3.js를 올렸다.

http://meteor.com/authcast 를 보면서
벡터그래픽을 마치 DOM처럼 쓰는 게 너무 맘에 들어서 https://atmosphere.meteor.com/ 에 올려야겠다는 생각이 들었음.

https://gist.github.com/3327056 와 https://atmosphere.meteor.com/wtf/package
내용 참조해서 https://github.com/acidsound/d3js-package 이런걸 올리긴 했는데

submodule 을 처음 써봐서 삽질 좀 했다.
js 라이브러리의 경로를 잘못써서 몇가지를 수정하고 올리려고 했더니

/usr/local/lib/node_modules/meteorite/lib/atmosphere.js:82
          throw "This tag has already been committed to the repo.";
          ^
This tag has already been committed to the repo.
이런 오류가 나서 소스를 열어보았다.



80      exec('git tag', function (err, stdout, stderr) {
81        if (stdout.indexOf(versionName) >= 0)
82          throw "This tag has already been committed to the repo.";

versionName 이 뭔가 쫓아가보았더니 smart.json 에서 지정한 "version".
뭐 암튼 release 할때 "version" 키의 값에 "v"를 앞에 붙여서 tag를 만들기 때문에
git tag -d "v<해당버전이름>" 해서 지우고 다시 하니 잘되더라.
내 경우엔 git tag -d v0.5.0 했음.

어 근데 메인 패키지에 d3 가 있네?
근데 지우는 방법을 모르겠네. 소스 지우는 부분이 딱히 없구만;
두고두고 창피할 거리가 하나 생겼네. 다음에 잘하면 되지뭐.

제 1회 Node.js Korea Conference 에 나갑니다.

http://nodeconf.kr/2012/
자세한 내용은 이쪽 참고하시고

저는 Meteor Tutorial 세션을 진행합니다만 연습해보니 시간 맞추기가 만만치 않네요.
Node.js의 코어 커미터와 컨트리뷰터들이 오전 세션을 엽니다. 엄청난 섭외력!

Isaac Z. Schlueter / Joyent - npm을 만드신 분이고 node.js 코어 커미터입니다.
Michael Rogers Node.js contributor and community Organizer - 죄송합니다. (_ _) 누군지 모르겠습니다.
Charlie Robbins / Nodejitsu - Joyent 와 쌍벽을 이루는 node.js 호스팅 서비스 회사인 Nodejitsu의 대표죠.

두근두근 합니다.
흥해라 Node.js!

2012년 10월 29일 월요일

주목할만한 Meteorite package들

meteorite 는 meteor 에서 정식으로 지원하지 않는 사도의 길이지만
그런 거 언제 중요했던가. 달면 뱉고 쓰면 삼키는 것이지 :)
meteor 자체가 여러가지로 부족하고 제약도 많은 프레임워크다.
하지만 워낙 매력있는 놈이다보니 자체 package manager 같은 게 만들어질 정도.
마치 Account package 처럼 meteor 1.0 찍기 전에 정식 기능으로 편입되지 않을까 하는 기대를 해본다.

지극히 개인적인 관점에서 흥미있는 것들이라 다른 것도 볼만하다.
혹은 없으면 직접 만들어서 커미터가 되어보자!

aloha-editor
Aloha Editor for Meteor

wysihtml5 쓰는데 문제가 너무 많아서 고려중

accounts-instagram
Login service for Instagram accounts.


OAuth 연동하는 예제로 볼만함

headly
meteor package to handle facebookexternalhit responses (for og:metatags use…

Meteor 에선 head 부분이 template 으로 들어가지 않아. 프로그램에서 접근이 불가하다.
이를 해결해주는 앱. facebook open graph 연동을 위해 필요함

node-modules
Node module hacks encapsulated
node-modules 을 쓰기 위해서 public 에 넣는 꼼수(tests 에 넣어도 되지만 package 할때 문제)를 피할 수 있게 직접 절대경로를 지정할 수 있음.


codemirror
CodeMirror repackaged for Meteor

jsbin등에서도 쓰는 code mirror 의 meteorite 패키지. 공동 문서작업 용도로 쓸만할 것으로 보임.

moment
Moment.js packaged for Meteor
시간 관련 함수를 깔끔하게 정리해놓은 moment. 추천

angularjs
Smartpackage for the use of angular on top of meteor
angular.js의 강력한 2-way binding 을 쓸 수 있도록 해주는 angular package. backbone 과 handlebars를 안쓰고 구현할 수 있다.

여전히 늘어나고 있는 추세고 업데이트도 잘되고 있다.
meteor도 그렇고 meteorite package들도 그렇듯이 이들이 사용하고 있는 js 라이브러리들을 잘 살펴보면 최근 경향을 한눈에 살펴볼 수 있어서 좋다.

2012년 10월 19일 금요일

map과 reduce를 사용하여 distinct(group by) 하는 방법

oracle 같은 RDB 를 다루던 시절엔

user | dept
-----------
john | 0000
tom | 0001
jack | 0000
jane | 0001

과 같은 형태의 테이블일때 부서의 목록을 뽑아 내고자 group by 나 distinct 를 사용하여

dept
-----
0000
0001

이와 같이 뽑아내곤 했는데

noSQL 을 사용하거나 array collection 형태의 데이터를 다룰때에도 이런 작업을 해야할 경우가 있다.

일단 예제로 다뤄볼 대상 collection 의 구조를 가정해 보자.
documents = [
  {
    _id: "........",
    message: "......",
    user : {
      _id: "........",
      profile: {
        displayName: "....."
      }
    }
]
흔한 게시판 형태의 데이터 구조라고 볼 수 있는데
각 documents 마다 고유한 _id 를 가지고 있고 해당 documents 를 작성한 user 객체가 각각 들어있는 경우라고 볼 수 있다.
그렇다면 documents 를 작성한 user 의 목록을 뽑아낼 수는 없을까?

node 콘솔이나 브라우저용 개발 툴에서 복사 붙이기 용으로 코드를 예제 코드를 한번 넣어보자면
documents = [ { _id: "doc0001", message: "document 01", user: { _id: "user0001", profile: { displayName: "john", } } }, { _id: "doc0002", message: "document 02", user: { _id: "user0002", profile: { displayName: "tom", } } }, { _id: "doc0003", message: "document 03", user: { _id: "user0001", profile: { displayName: "john", } } },
{ _id: "doc0004", message: "malformat" },
]
이런 식으로 넣었을때
[
Object
  1. _id"doc0001"
  2. message"document 01"
  3. userObject
    1. _id"user0001"
    2. profileObject
      1. displayName"john"
      2. __proto__Object
    3. __proto__Object
  4. __proto__Object
,
Object
  1. _id"doc0002"
  2. message"document 02"
  3. userObject
    1. _id"user0002"
    2. profileObject
      1. displayName"tom"
      2. __proto__Object
    3. __proto__Object
  4. __proto__Object
,
Object
  1. _id"doc0003"
  2. message"document 03"
  3. userObject
    1. _id"user0001"
    2. profileObject
      1. displayName"john"
      2. __proto__Object
    3. __proto__Object
  4. __proto__Object
,
Object
  1. _id"doc0004"
  2. message"malformat"
  3. __proto__Object
]
요런 구성으로 들어갈 것이다.
마지막 doc0004는 user 가 없는데 당연히 저런 데이터가 있을 수 있다.
왜냐면 관계형 DB 처럼 고정 스키마가 없기 때문이다.

그러면 먼저 map 을 이용하여 user만 추출해보도록 하자.
documents.map(function(v) { return v.user && v.user.profile && v.user.profile.displayName ? v.user:null; });
[
Object
  1. _id"user0001"
  2. profileObject
    1. displayName"john"
    2. __proto__Object
  3. __proto__Object
,
Object
  1. _id"user0002"
  2. profileObject
    1. displayName"tom"
    2. __proto__Object
  3. __proto__Object
,
Object
  1. _id"user0001"
  2. profileObject
    1. displayName"john"
    2. __proto__Object
  3. __proto__Object
, null]
마지막 user가 없는 경우는 조건식을 통해 null을 반환하도록 하였다.
그러면 이제 중복을 제거할 차례다.
중복을 제거하기 위해 reduce 를 사용하자.
reduce는 두개의 인자를 갖는 function을 사용하는데

document.reduce(function(a,b) {
  return ...
});

이런 식으로 사용한다.
reduce 함수의 재미있는 점은 인자를 가져가는 방식이다

index | a | b
0 | array[0] | array[1]
1 | 0번째의 return 값 | array[2]
2 | 1번째의 return 값 | array[3]
......
n-1 | n-2번째의 return 값 | array[n]
------------------------------------
최종값 = n-1번째의 return 값
되시겠다.

이런 특성을 이용하여 중복이 발생하지 않는 user의 array를 만든다면 다음과 같은 전략을 짤 수 있다.

  1. reduce function안에서 a,b 두개를 받는다.
  2. a 즉 지난번 reduce 실행값 중 b가 있는지 검사한다.
  3. 있으면 a 자신을 반환한다.
  4. 없으면 a에 b를 추가하고 a 자신을 반환한다.

단순한 로직인데 a가 일단 array라는 가정이 필요하니 첫번째 시도때 a를 []로 초기화 해줘야하고 b가 a안에 있는지 여부를 검사하기 위해 some 이라는 function 을 사용하여 _id가 같은 것을 발견하면 true를 주게끔 해보자.
아, 그리고 null은 의도한 결과가 아니므로 당연히 예외처리.

documents.map(function(v) { return v.user && v.user.profile && v.user.profile.displayName ? v.user:null; }).reduce(function(a,b) { a=[].concat(a); if(b && !a.some(function(v) { return v._id===b._id; })) { a.push(b); } return a; });
[
Object
  1. _id"user0001"
  2. profileObject
    1. displayName"john"
    2. __proto__Object
  3. __proto__Object
,
Object
  1. _id"user0002"
  2. profileObject
    1. displayName"tom"
    2. __proto__Object
  3. __proto__Object
]



어떠십니까? 엘레강스하십니까?

쓰고보니 재탕 느낌이 들어서 검색해보니 예전에 이런 글을 쓴적이 있긴 하네
http://spectrumdig.blogspot.kr/2012/03/javascript-union.html
그래도 map이랑 같이 썼으니 인정.


드디어 meteor 0.5.0!

이전에 썼던 auth 관련 글은 죄다 잊으십시오.
그냥 meteor update 하면 됩니다.
Accounts 기능이 정식으로 들어갔습니다.
일반 로그인, OAuth 로그인이 정말 간편하게 됩니다.
사용법은 http://docs.meteor.com/#accounts_api 참조하세요.

만세! 만세! 만만세!

screencast 도 꼭 보세요. 두번 보세요. 아니 세번 보세요.

만세! 만세! 만만세!


2012년 10월 13일 토요일

deploy 서버에서 mongo data를 dump 하는 법

meteor deploy YOURSITE.meteor.com

이 명령으로 디플로이 했을 시
테스트로 잠깐 쓰다가 제대론 서버에 올려서 돌리고 싶을 수 있다.

meteor mongo -U YOURSITE.meteor.com
하면 접속 정보가 나온다.

내 경우엔
mongodb://client:72c39268-747e-9380-7538-ed317e096c69@skybreak.member1.mongolayer.com:27017/YOURSITE_meteor_com
이렇게 나오는데 //client: 뒤에 있는 놈이 암호.
1분 마다 바뀐다.

mongodump -u client -h skybreak.member1.mongolayer.com:27017 -d YOURSITE_meteor_com -p 72c39268-747e-9380-7538-ed317e096c69

잽싸게 접속하는 것이 포인트.
각자 상황에 따라 URL 이 다를 수 있다.

mongodump 로 내려놓은 데이터를 복원하려면 mongorestore 를 사용한다.
만일 로컬 환경에 복원한다면

> /usr/local/bin/meteor mongo -U
mongodb://127.0.0.1:3002/meteor

로 환경을 확인한 후


mongorestore --host 127.0.0.1:3002 ./dump/YOURSITE_meteor_com --drop -d meteor


와 같이 복원하면 OK

만일 특정 collection 별로 하려면 mongoimport / mongoexport 를 사용하면 된다.

2012년 10월 12일 금요일

meteor auth branch 가 RC (Release candidated) 버전으로!

기존 문서(https://github.com/meteor/meteor/wiki/Getting-Started-with-Auth)는 deprecated인 상태이므로 http://auth-docs.meteor.com/ 쪽으로 봐야합니다.
Accounts 객체에 대한 내용이 있습니다.

지난 버전에서 사용하던 Accounts.configuration 객체는 Accounts.loginServiceConfiguration 으로 변경해야합니다.
내용은 여전히 아래의 구조로 insert 하여야합니다.
{
  "service":"facebook",
  "appId":"xxx4xxxx8x8xxxx",
  "secret":"3xxf8xxxx1d4cxxxx018dfxxxxb6a7bc"
}
서비스명(facebook, google, twitter, github, weibo 등등), 앱 아이디, 앱 비밀번호 넣는거죠.

일단 branch가 auth 에서 드디어 devel (https://github.com/meteor/meteor/tree/devel)으로 통합이 되었습니다.
다음이나 다다음버전 쯤에선 고생하지 않고 한방에 설치 가능하겠네요.

아직까지는 Slow start(https://github.com/meteor/meteor#slow-start-for-developers) 방식으로 설치하여야합니다.

지난 글(http://spectrumdig.blogspot.kr/2012/08/meteor-039.html) 참조하시고
변경된 부분은 기존에 git clone 을 통해 받았던 경로에서

git checkout -t origin/auth

대신

git checkout -t origin/devel
을 하시고

git pull
./install.sh
하시면 됩니다.

뒤집어지면 다시 포스팅 하겠습니다. xD

2012년 10월 8일 월요일

Fiber, future, 그리고 co-routine

맞춤법 검사를 위해 서버사이드에서 http.get 을 할 일이 있는데
meteor 에서 지원하는 future(node-fiber 라이브러리에 포함) 를 사용했다.

이거 블록으로 되어서 동시접속시에 망하는 거 아니냐라는 의혹을 불식시키기 위해
간단하게 코드를 짜보았다.

meteor create future
cd future

vi future.js
하고 아래와 같이 부다다.

if (Meteor.isClient) {
  Template.hello.greeting = function () {
    return "Welcome to future.";
  };

  Template.hello.events({
    'click input' : function () {
      // template data, if any, is available in 'this'
      if (typeof console !== 'undefined')
        console.log("You pressed the button");
    }
  });
}

if (Meteor.isServer) {
  Future = Npm.require('fibers/future');
  Meteor.startup(function () {
    // code to run on server at startup
    Meteor.methods({
      'future':function() {
        console.log('future start:'+Date.now());
        var fut = new Future();
        Meteor.setTimeout(function() {
          console.log('callback end:'+Date.now());
          fut.return('yahoo:'+Date.now());
        }, 1000);
        console.log('future end:'+Date.now());
        return fut.wait();
      }
    });
  });
}

(노란색 형광펜으로 최근 Meteor 적용사항 수정. 기존 ret가 사라지고 return로 바뀌었다)
future 라는 서버사이드 method 를 만들었으니 브라우저 콘솔에서 meteor.call('future') 를 호출해서 결과를 관찰해보도록 하자.

client side console
>> Meteor.call('future', function(err, result) { console.log(result) }); console.log('after call:'+Date.now());
after call:1349676901674
yahoo:1349676902879

server side log
future start:1349676901679
future end:1349676901679
callback end:1349676902679

after call -> future start | future end -> callback end | yahoo

순으로 실행이 된다.
call 후 다음 실행은 계속 진행.

Continuation-Passing Style 의 경우 future의 인자로 callback 을 계속 끌고 들어가야하므로 method signature 가 동기와 비동기 차이가 발생하지만 이 경우엔 그렇지 않다.

2012년 10월 5일 금요일

headless ubuntu for dummies


재료
가상환경 빨리 구축하기.
http://www.kbench.com/software/?flag=1&pcc=0&pg=1&no=48219
VirtualBox 는 여기에서

ftp://ftp.sayclub.com/ubuntu-releases/12.10/
우분투 바이너리는 여기.

나의 선택은?
ubuntu-12.10-beta2-server-i386.iso

1.
[!] Configure the network
Hostname:
ubuntu <default>
2.
[!!] Set up users and passwords
Full name for the new user:
noder <풀 이름>
3.
[!!] Set up users and passwords
Username for your account:
noder <계정명>

그다음 password 입력, 만일 단순한 패스워드를 넣으면 weak password 쓸 거냐고 묻는데 알아서.

4.
[!!] Partition disks
Write the changes to disks and configure LVM?
<Yes>

5.
[!!] Partition disks
Write the changes to disks?
<Yes>

6.
[!] Configure the package manager
HTTP proxy information (blank for none):
<Continue>

설치 후 해당 버추얼 머신의 설정을 선택
설정/네트워크/어댑터1
다음에 연결됨(A) - 브리지 어댑터

확인

다시 VirtualBox 머신을 기동하면 네트웍 설정 변화 감지.
로그인 후 ifconfig 에서 eth0 를 보면
inet addr 이 별도로 생성되었음. 내 경우 192.168.0.18

sudo aptitude update
sudo aptitude install openssh-server

연타해서 openssh 설치

그리고 ssh 프로그램으로 접속 확인.

잘되면 sudo init 0 해서 머신 종료
headless 모드로 띄우자.

VmHeadless.exe -s <가상장치명>
아님 조용하게
"C:\Program Files\HSTART\hstart64.exe" /NOCONSOLE /SILENT "C:\Program Files\Oracle\VirtualBox\vboxheadless.exe --startvm MYVM"

이렇게 띄울 수도 있다고 한다.

VmServiceControl 다운로드 후 실행. 반드시 관리자 모드로

2012년 10월 4일 목요일

meteor 에서 npm 을 이용해 node_modules 을 사용하는 법

제대로 쓴 곳이 없어서 결국 직접 해보고 알아내는 수 밖에 없었는데
서버쪽 moment.js 를 쓰고 싶어서
server/client 쪽 양쪽 다 영향을 안 받는 디렉토리 중 하나인 tests 디렉토리 아래서

npm install moment

를 실행하고

npm init

해서 대충 package.json 파일을 만들고


Meteor.startup(function () {
  // server side require
  var require = __meteor_bootstrap__.require;
  var path = require('path');
  var base = path.resolve('.');
  var moment = require(path.resolve('.')+'/tests/node_modules/moment');

이런 식으로 했더니 일단 잘 되더라.
근데 문제는 meteor deploy 를 하거나 meteor bundle 로 일반 node.js 프로젝트 화 했을때
test 디렉토리를 프로젝트에 포함시키지 않아서 서버쪽 require 를 쓸 수 없게 된다.

그래서 매우 찜찜하지만 public 경로 안에 node_modules 를 넣는 것 이외엔 방법이 없다.
public
|-node_modules
|-.gitignore
|-package.json

요런식으로 구성했다.
그리고 서버쪽 코드를 좀 많이 손을 대야하는데 로컬에서도 돌아가고 디플로이 한 서버에서도 돌아가도록 맞춰줬다.

Meteor.startup(function () {
  // server side require
  var require = __meteor_bootstrap__.require;
  var path = require('path');
  var base = path.resolve('.');
  var isBundle = path.existsSync(base + '/bundle');
  console.log('deploy mode: %s', isBundle ? 'bundle' : 'meteor');
  var modulePath = base + (isBundle ? '/bundle/static' : '/public') + '/node_modules';

  // code to run on server at startup
  var moment = require(modulePath + '/moment');

bundle 경로를 기준으로 deploy 상태를 판별하고 개발시엔 public/node_modules 을 기준으로 deploy 시엔 bundle/static/node_modules 를 기준으로 외부 모듈을 require 하면 된다.

서버사이드 로그도 찍어보고 fs.readdir로 일일이 디렉토리 구조까지 열어보면서 욕 좀 봤다.

2012년 9월 26일 수요일

Meteor auth branch 변경 사항. 0.4.1 버전 9/22 기준

http://spectrumdig.blogspot.kr/2012/08/meteor-039.html 이후로 업데이트가 좀 되었다.

api key/secret 을 더 이상 소스 안에서 관리 안함.
accounts-ui 를 사용할 것을 권장하고 있다.

$ meteor mongo 로 접근
> db["accounts._loginServiceConfiguration"].find()
{ "service" : "facebook", "appId" : "appID", "secret" : "app Secret", "_id" : "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxx" }
이와 같은 형태로 설정.

재설정시 서비스 명을 기준으로 remove
> db["accounts._loginServiceConfiguration"].remove({service:"facebook"})

그룹스의 글을 일단 옮겨와 본다.
기존에 setSecret method 및 config method 는 전부 지우고 Meteor.accounts.configuration collection 에 넣으라는 소리를 하는데

그것만으론 안된다. appID하고 appSecret은 지우고 collection 에 넣든 accounts-ui 를 써서 다시 지정했다고 해도 OAuth 인증 후 callback 으로 돌아오는 URL이 무조건 localhost:3000으로 되는 오류가 있는데 ROOT_URL 환경변수를 어플리케이션의 URL로 설정하던가
__meteor_runtime_config__.ROOT_URL = "http://<callback 받을 어플리케이션 URL>" ;
로 서버쪽 startup function 안쪽에 넣던가 하라고 한다.
그룹스 없으면 어쩌려고 이놈들이;;

출처는 https://groups.google.com/d/msg/meteor-core/cVyomNZJzBg/3GdyEmkgtQgJ

이래저래 해봐도 다 안되서 그냥 환경변수 잡았다. 그리고 서버사이드에서 초기화 할때 collection을 잡았다.

결론은 아래 3단계 작업을 해주면 된다.

  1. meteor 기동전에 환경 변수를 잡는다.

    export ROOT_URL=http://<callback 받을 어플리케이션 URL>
  2. server쪽에 startup function 안에 텅빈 Meteor.accounts.configuration collection에 설정을 insert 한다.

    config = Meteor.accounts.configuration
    config.insert({ "service" : "facebook", "appId" : "appID", "secret" : "app Secret"});
  3. meteor 기동
기존에 Meteor.user() 를 사용했다면 오브젝트 구조가 바뀐 것에 주의하자.
Meteor.user().name 을 사용했다면 Meteor.user().profile.name 을 사용해야한다.

* PS : accounts-passwords 패키지가 accounts-password 로 단수형이 되었다 (....)

2012년 9월 18일 화요일

시간 종결자 moment.js


http://momentjs.com/
날짜 종결자라고 봐야겠다.
time ago 찾다가 발견. formatting에서부터 i18n까지 거의 대부분의 스펙을 제공함.

한가지 기능만 살펴보면
블로그나 SNS 등에서 쓰는 상대시간(humanize time이라고 하더라)도 지원.
http://momentjs.com/docs/#/displaying/from/ 의 내용을 참조하면
var start = moment(); // 현재시각 기준
혹은
var start = moment(1347961213698);
식으로 초기화하고

start.fromNow();
로 얼마나 시간이 지났는지를 humanize time 으로 반환한다.
a few second 대신 now 를 사용하고 싶다면
moment.relativeTime 를 수정하여

moment.relativeTime.s='now';
moment().fromNow(true);
이런식으로 true 옵션을 줘서 ago를 떼어내고 임의로 지정할 수 있다.

2012년 9월 17일 월요일

cloud9ide remote access tip

웹브라우저만 있으면 파일 업로드까지 가능한 막강 웹IDE인 cloud9ide 가 드디어 node 0.8.x 를 지원한다.
하지만 engine.io 탑재와 함께 내부기작이 바뀌었는지
외부에서 사용하기 위해 -l 옵션을 줘서 0.0.0.0 을 지정하면 클라이언트쪽 자바스크립트에서도 0.0.0.0을 참조하는 구조로 바뀌었다.
그래서 로컬로만 사용가능하고 외부에서 사용시 클라이언트쪽 통신이 끊어질 수 밖에 없는데
이래서야 원격 협업툴이 되지 않지.
그렇다고 직접 공인 IP를 찌르면 EADDRNOTAVAIL 오류가 난다.

$ node cloud9/server.js -w ~/workspace -p 8123 -l 123.123.123.123
path.existsSync is now called `fs.existsSync`.
connect plugin start

events.js:66
        throw arguments[1]; // Unhandled 'error' event
                       ^
Error: listen EADDRNOTAVAIL
    at errnoException (net.js:768:11)
    at HTTPServer.Server._listen2 (net.js:891:19)
    at listen (net.js:935:10)
    at Server.listen (net.js:992:9)
    at dns.js:71:18
    at process.startup.processNextTick.process._tickCallback (node.js:244:9)

엄청 오랫동안 이 문제로 헤맸는데
결국 클라이언트는 접속할 도메인을 사용하게 하고
서버 사이드에서만 0.0.0.0 을 사용하면 되는 문제이지 않을까 싶어서
/etc/hosts 를 열고

c9.mydomain.com    0.0.0.0

이부분을 추가. 물론 c9.mydomain.com 대신 자신이 사용하는 도메인을 쓰자.
다행스럽게도 잘 된다. 아아 다행이야 다행.

물론 기동은 -l 옵션 줄때 내 도메인을 주자

$ node cloud9/server.js -w ~/workspace -p 8123 -l c9.mydomain.com
이런 식.
-w 는 작업디렉토리
-p 는 포트번호
-l 이 접속 주소인데
/etc/hosts 설정에 따라 0.0.0.0 과 같은 것이 되어서 내부적으로는 -l 0.0.0.0 과 동일한 셈이다.
사악한 꼼수지만 크게 건들지 않아도 되는 간단한 해결책.

2012년 9월 9일 일요일

Meteor 테스트 전략

* package를 만들고 테스트 할 때
/usr/local/meteor/packages/livedata 참조
package.js 내용을 보면


Package.describe({
  summary: "Meteor's latency-compensated distributed data framework",
  internal: true
});

Package.on_use(function (api) {
  api.use(['stream', 'uuid']);
  api.use(['json', 'underscore', 'deps', 'logging'], ['client', 'server']);

  // livedata_connection.js uses a Minimongo collection internally to
  // manage the current set of subscriptions.
  api.use('minimongo', ['client', 'server']);

  api.add_files('writefence.js', 'server');
  api.add_files('crossbar.js', 'server');

  api.add_files('livedata_common.js', ['client', 'server']);

  api.add_files('livedata_connection.js', 'client');

  api.add_files('livedata_server.js', 'server');


  api.add_files('client_convenience.js', 'client');
  api.add_files('server_convenience.js', 'server');
});

Package.on_test(function (api) {
  api.use('livedata', ['client', 'server']);
  api.use('mongo-livedata', ['client', 'server']);
  api.use('test-helpers', ['client', 'server']);
  api.use('tinytest');

  api.add_files('livedata_connection_tests.js', ['client']);
  api.add_files('livedata_tests.js', ['client', 'server']);
  api.add_files('livedata_test_service.js', ['client', 'server']);
});

이런 식으로 정의해놓았다.
add_files 펑션에 각각 클라이언트/서버 사이드를 지정하고 해당 js 테스트를 돌릴 수 있도록 지정한다.

만일 livedata package 를 수정했고 테스트 여부를 확인하고 싶으면 단순하게

$ cd /usr/local/meteor/packages/livedata
$ meteor

[[[[[ /usr/local/meteor/packages/livedata ]]]]]

Running on: http://localhost:3000/


한뒤 localhost:3000 을 보면 테스트를 수행하는 것을 확인할 수 있다.

* application 에서 test 를 만들때
최상위 경로에 tests (test 가 아니다 복수형으로 써야함) 디렉토리를 만들면
meteor 에서 tests 경로 아래에 있는 것들은 가져오지 않는다. (중요!)
가끔 서버사이드에서 돌아가는 것들을 public 에 넣어서 돌리는 분들이 있는데
워험천만한 행동입니다.
tests에 넣고 하세요.

2012년 9월 8일 토요일

webstorm 에서 meteor Application 을 기동하는 방법

webstorm 은 훌륭한 web-IDE고
meteor도 훌륭한 javascript framework 인데

meteor의 경우 실제로 node.js 파일을 실행하는 부분이 없어서 어딜 진입점으로 해야할지 난감하게 느낄 수도 있다.
고민하지 말고 meteor 실행 파일을 까보자.

cat `which meteor`
해보니 맨 마지막 두줄에


export NODE_PATH="$DEV_BUNDLE/lib/node_modules"
exec "$DEV_BUNDLE/bin/node" "$METEOR" "$@"

이 두문장이 보인다.

$DEV_BUNDLE 이 어딘지 읽어보기 귀찮아서 찍어봤더니 meteor 설치 디렉토리더라
내 경우는 /usr/local/meteor
which meteor 에서 bin 상위 경로라고 보면 된다.
Edit Configuration 에서 Node.js 를 추가하고 아래와 같이 설정해보자.



이런식으로 주니 무리가 없더라. Environment variables 에
NODE_PATH 항목을 추가하고 meteor 를 설치한 경로 + /lib/node_modules 를 추가하는게 포인트.
눈치빠른 분이라면 meteor application 에 외부 package를 추가하고자 할때 어떻게 해야할지 감 잡으신 분도 있을거다

아무튼 저렇게 하고 가동하면 정상적으로 동작한다.
이제 터미널 옮겨다니 말고 편하게 콘솔창으로 서버 메시지를 보자.

2012년 9월 1일 토요일

node.js 에서 system call을 사용하는 방법


cloud9 을 사용해서 작업하고 있는데
업로드는 드래그&드랍으로 훌륭하게 지원하는 반면 다운로드를 지원하지 않아 조금 아쉽다.
child_process 패키지를 사용하여 폴더를 전체를 압축한 다음 내려주자.
http://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options
spawn을 사용해서 만들었다.

대충 express 를 통해 파일을 만들고 route/index.js 를 다음과 같이 해보았다.
/*
 * GET home page.
 */
var spawn=require('child_process').spawn;
var fs=require('fs');
exports.index = function(req, res) {
var zip=spawn('zip',['-r', 'archive.zip', '.', '-i', '*']);
zip.stdout.on('data', function (data) {
console.log('stderr: ' + data);
});
zip.stderr.on('data', function (data) {
console.log('stderr: ' + data);
});
zip.on('exit', function() {
res.setHeader('Content-type', 'application/octet-stream')
res.setHeader('Content-disposition', 'attachment; filename=archive.zip');
res.write(fs.readFileSync(__dirname+'/../archive.zip'));
res.end();
});
}

stdout.on -> 표준 출력 가져오기
stderr.on -> 오류 출력 가져오기

zip 파일로 내려주기 위해 Content-type 과 파일명을 setHeader 를 통해 붙여주자.
참 쉽죠?

2012년 8월 27일 월요일

Meteor 에서 REST 사용

Meteor 에서 file-upload 라던가 REST API 같은 걸 제공하려면
request 를 받아서 처리할 수 있어야하는데 Meteor 에선 지원하지 않아 좀 답답한 면이 있다.

하지만 그래봤자. Node.js 고 connect 프레임워크를 사용하고 있기 때문에
저번에 언급한 적이 있던 __meteor_bootstrap__ 를 이용해보자.

./.meteor/local/build/server/server.js 를 보면 서버쪽 구동부를 볼 수 있는데
run function 쪽을 보면


  __meteor_bootstrap__ = {require: require, startup_hooks: [], app: app};

이런 코드가 있다.
여기서 app은?

그 위쪽에 

  var app = connect.createServer();

어이쿠 connect 네.
게임 끝나셨다.

서버쪽에 아래와 같이 stack array 에 route 를 추가하면 된다.

Meteor.startup ->
  app=__meteor_bootstrap__.app
  app.stack.unshift
    route: "/api",
    handle: (req,res)->
      res.statusCode = 200
      res.write "OK"
      res.end()

다행이야 다행이군!

2012년 8월 26일 일요일

meteor 0.3.9 에서 사용자 인증을 써보자.

예전에 everyAuth 에 대한 글을 쓴적이 있는데
https://github.com/meteor/meteor/wiki/Getting-Started-with-Auth
meteor wiki에 위와 같은 글이 있더라.
발빠르다 meteor. 투자받더니 탄력이 붙었는지 쭉쭉 잘나가는군.
일단 meteor 설치부터 다시하자.
이전에 Quick start 가이드대로

curl https://install.meteor.com | /bin/sh

이렇게 설치했지만.
이번엔 git 에서 auth Branch를 따서 갈 것이므로 수동 설치하자.

git clone git://github.com/meteor/meteor.git
cd meteor
수동설치래봤자 적절한 곳에 clone 하고
인스톨 하는게 전부인데.

auth는 다른 branch에 있기 때문에 일단 clone 하고 해당 경로에 진입만한 상태에서
git branch -r 로 리모트 브랜치를 확인해보자

  origin/HEAD -> origin/master
  origin/auth
  origin/auth-email
  origin/auth-test-isolation
  origin/auth-twitter
  origin/avital
  origin/avital-remove
  origin/avital-watch
  origin/david-handlebars
  origin/dev-bundle-bump
  origin/devel
  origin/email
  origin/forms
  origin/jade
  origin/master
  origin/release-0.1
  origin/release-0.1-templates
  origin/spark
  origin/spiderable
  origin/test-isolation
  origin/version-bump
  origin/webgl
  origin/wrappedjs
  origin/{ref}squashed-auth

이렇게 주욱 나올텐데 우리가 필요한 건

  origin/auth

요놈이다.
git checkout -t origin/auth
(0.4.2 기준 git checkout -t origin/devel 로 변경)
해서 브랜치를 변경하고

$ git branch
* auth
  master

이렇게 확인해서 auth로 옮겨간 것을 확인하고 install.sh 를 실행해주자.
./install.sh
그 다음 내용을 확인해보면


$ meteor list
absolute-url            Generate absolute URLs pointing to the application
accounts                A user account system
accounts-facebook       Login service for Facebook accounts
accounts-google         Login service for Google accounts
accounts-passwords      Password support for accounts.
accounts-twitter        Login service for Twitter accounts
accounts-ui             Simple templates to add login widgets to an app.
accounts-weibo          Login service for Sina Weibo accounts
amplify                 Cross browser API for Persistant Storage, PubSub and Req
autopublish             Automatically publish all data in the database to every
backbone                A minimalist client-side MVC framework
bootstrap               UX/UI framework from Twitter
code-prettify           Syntax highlighting of code, from Google
coffeescript            Javascript dialect with fewer braces and semicolons
email                   Send email messages
force-ssl               Require this application always use transport layer encr
handlebars              Simple semantic templating language
htmljs                  Easy macros for generating DOM elements in Javascript
http                    Make HTTP calls to remote servers
insecure                Allow all database writes by default
jquery                  Manipulate the DOM using CSS selectors
jquery-history          pushState module from the jQuery project
jquery-layout           Easily create arbitrary multicolumn layouts
jquery-waypoints        Execute a function when the user scrolls past an element
less                    The dynamic stylesheet language.
localstorage-polyfill   Simulates the localStorage API on IE 6,7 using userData
madewith                Made With Meteor badge
sass                    Sassy CSS pre-processor.
showdown                Markdown-to-HTML processor
spiderable              Makes the application crawlable to web spiders.
stylus                  Expressive, dynamic, robust CSS.
underscore              Collection of small helper functions (map, each, bind, .

못보던 package들이 많이 생겼는데 accounts 라는 것들이 보인다. 심지어 weibo 도 있네;;

여기까지 왔으면 이제 사용하면 된다.


meteor create auth_example
cd auth_example
해서 만들고
meteor add accounts-google accounts-facebook accounts-twitter accounts-passwords accounts-ui
google, facebook, twitter, 일반인증까지 전부 넣자.

auth_example.html 을 열어서

<head>
  <title> auth_example </title>
</head>

<body>
  {{> loginButtons}}
  {{> hello}}
</body>

<template name="hello">
  <h1>Hello World!</h1>
  {{greeting}}
  <input type="button" value="Click" />
</template>


loginButtons 라는 template 을 추가해주자.
아마 브라우저에 Sign in 이라는 링크가 보일테고
Sign in with Facebook 같은 걸 누르면

Facebook API key not set. Configure app details with Meteor.accounts.facebook.config() and Meteor.accounts.facebook.setSecret()

이렇게 나올거다.
설정해주자.
https://developers.facebook.com/apps/<APP_ID>/summary?save=1 에서 만들어놓았던

App ID, App Secret, Website with Facebook Login 의 URL

이렇게 세가지를 가져와서
client/server 양쪽에 들어가는 공통 js 에
Meteor.accounts.facebook.config(APP_ID, APP_URL);

App ID, URL 을 각각 넣어주고
server 쪽 js 에
Meteor.accounts.facebook.setSecret(APP_SECRET);

App Secret 을 넣어준다. 당연히 Secret 은 노출되면 안되니까.

이번 업데이트로 필요없어졌다. app정보는 Collection 안에 들어간다. http://spectrumdig.blogspot.kr/2012/09/meteor-auth-branch-922.html
내용에 다시 정리하였다.

나머지 내용은
https://github.com/meteor/meteor/wiki/Getting-Started-with-Auth 를 참조하자.

(0.4.2 관련 최신 내용)
http://spectrumdig.blogspot.kr/2012/10/meteor-auth-branch-rc-release-candidated.html

2012년 8월 22일 수요일

angular.js + node.js + express 로 JSONP 구현을 해보았다.

크로스도메인 문제(다른 사이트의 ajax를 호출할 수 없는 브라우저의 보안정책)를 피하기 위해 JSONP라는 대안이 있는데
angular를 이용해 간단하게 목록을 가져오는 외부 호출을 만들어보자.
http://docs.angularjs.org/api/ng.$http#jsonp
내용을 보고 참조하였다.

index.html
<!DOCTYPE html>
<html ng-app>
<head>
    <title></title>
    <script src="http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.0.1/angular.min.js"></script>
    <script type="text/javascript" src="todo.js"></script>
</head>
<body>
<div>
    <div ng-controller="listController">
        <ul>
            <li ng-repeat="list in lists">
                {{list.author}}
            </li>
        </ul>
    </div>
</div>
</body>
</html>

ng-app 을 설정하고 listController 에 lists 객체로부터 author 목록을 가져오게 하자.

todo.js

listController = function($scope,$http) {
    angular.extend($scope,{
        query:function() {
            $http.jsonp('http://jsbin.com/uqamef/1?callback=JSON_CALLBACK&a=bbb').success(function(data){
                $scope.lists=data;
            })
        }
    });
    $scope.query();
}

listController 에 데이터 바인딩을 위한 $scope와 ajax 호출을 위한 $http를 각각 인자로 두고
jsonp 호출 후 성공시 $scope.lists 에 결과값을 넣도록 한다.
URL은 해당 JSONP를 구현한 API URL과 "?callback=JSON_CALLBACK" 부분(중요!)
그리고 파라메터 값인 "&key=value..." 형태로 구성하여 $http.jsonp 메서드로 호출한다.

서버쪽은 오히려 간단한데
jsbin.com 같은 걸 이용해서 구현하면
html 쪽은 지우고 javascript 쪽에만 jsonp 형태의 데이터를 기술하여
http://jsbin.com/uqamef/1 처럼 테스트 데이터를 만들어 볼 수 있으며

실제 구현은 단지 res.json으로 결과를 던지면 된다.

... 전략 ...
app.get("/jsonp", function(req,res) {
  console.log(req.query.callback);
  console.dir(req.param("parameter name"));
  var result = [
    {"author":"spectrum"},
    {"author":"singajong"}
  ];
  res.json(result);
});
... 후략 ...

근데 요즘은 JSONP 잘 쓰나? 크록포드옹이 'Bad thing' 이라고도 말하기도 했고
실제로 펑션이 와리가리 하는 형태라서 보안문제도 있는데 말이지.

* 사족: 만일 express 3.0 이전버전을 사용한다면 JSONP형식이 아니라 JSON형식으로 나올 수 있다. 아래와 같이 app.configure 블록에 jsonp callback 을 사용한다고 명시하자


app.configure(function(){
  app.set("jsonp callback", true);
 ...
});