2013년 10월 25일 금요일

Genymotion 에서 Unroot/Root 상태 전환하기 위한 쉬운 방법.

Genymotion은 지옥 성능의 안드로이드 에뮬에 비해 확실히 쾌적하고 아름다운 툴이다.
최근 롬을 보면 기본 Su 설치 상태로 나오는데
일부 Root을 검사하는 앱에서 사용 제한이 걸리곤 한다.

멀티터치 같은 걸 시뮬하기 위해서 DroidMote를 사용한다던가 하는 용도를 봤을땐 Root가 필요하고
여러가지 이유로 개발환경에선 Root/Unroot를 다 쓸 수 있으면 좋다.

Genymotion Configuration 아이콘을 찍고
Save 버튼 위 Always allow su access (bypass Superuser app)에 체크박스를 켜준다.

Save 하면 아마 재부팅을 한다고 할 것이다. 재부팅하자.
요즘은 SuperSu 가 간단하니 SuperSu를 스토어에서 받자.

몇몇 가지를 묻는데 특별한 롬을 쓰느냐 물을 때 Normal 이랑 충돌이 있으니 기존 Su앱을 지우겠느냐에서 등등 상식적인 걸 체크하고

SuperSu를 다시 실행. 이제부턴 설정창에서 Su를 간편하게 껐다 켰다 할 수 있다.

2013년 10월 24일 목요일

meteor 에서 E-mail 인증하기


MAIL_URL은 gmail을 쓸 경우 다음과 같이 설정하면 된다.

smtp://사용자계정%40gmail.com:계정암호@smtp.gmail.com:465/

혹은 google apps 계정은 경우

smtp://사용자계정%40구글Apps도메인:계정암호@smtp.gmail.com:465/

----
가입자 확인 메일 발송엔 몇가지 문제가 있는데
1. Text로 메일을 보낸다.
2. Formatted 형식을 사용할 수 없다.

이건 내가 생각해봐도 좀 아니다 싶다.
server 쪽에서 startup 할때 기존 Accounts.emailTemplate과 Accounts.sendVerificationEmail 등등이 제대로 작동하도록 바꿔치기 해주자.

:: email.coffee

Meteor.startup ->
  # set Mail configuration
  Email = Package.email.Email;

  _.extend Accounts.emailTemplates,
    from: "Gearlounge <no-reply@gearlounge.com>"
    verifyEmail:
      subject: (user)->
        "#{"#{user.profile.displayName}님, "if (user.profile and user.profile.displayName)?}가입을 환영합니다."
      html: (user, url)->
        Handlebars.templates['verifyEmail'](
          user: user.profile && user.profile.displayName
          verifyEmailUrl: url
          urlHome: Meteor.absoluteUrl()
        )
  # hack for handlebars-server
  _.extend Accounts.sendVerificationEmail = (userId, address) ->
    user = Meteor.users.findOne userId
    throw new Error "Can't find user" if not user?
    if not address?
      email = _.find user.emails || [], (e) -> !e.verified
      address = (email || {}).address

    throw new Error "No such email address for user." if not address or not _.contains(_.pluck(user.emails || [], 'address'), address)

    tokenRecord =
      token: Random.id()
      address: address
      when: new Date()
    Meteor.users.update
      _id: userId
    ,
      $push:
        "services.email.verificationTokens": tokenRecord
    verifyEmailUrl = Accounts.urls.verifyEmail tokenRecord.token
    Email.send
      to: address
      from: Accounts.emailTemplates.from
      subject: Accounts.emailTemplates.verifyEmail.subject user
      html: Accounts.emailTemplates.verifyEmail.html user, verifyEmailUrl
    return

위 코드는 기존 accounts-password.js의 Accounts.emailTemplates을 그대로 옮겨(물론 coffeescript로 바꾸긴 했지만) 놓은 수준이다.
하지만 노랑색으로 그어 놓은 부분이 변경한 부분인데 기존 Accounts.emailTemplates는 text밖에 사용할 수 없어서 html을 인자로 주었고 메일 양식도 하드코딩된 부분을 handlebars-server(https://github.com/EventedMind/meteor-handlebars-server) 패키지를 사용하여 양식화하였다.

      html: (user, url)->
        Handlebars.templates['verifyEmail'](
          user: user.profile && user.profile.displayName
          verifyEmailUrl: url
          urlHome: Meteor.absoluteUrl()
        )

위 코드에서 보듯 verifyEmail 이란 template 을 사용하는데 server쪽 디렉토리 아래 적당히 verifyEmail.handlebars 라는 파일을 생성하면 알아서 잘 찾는다.
은근 괜찮음 추천추천.

서버사이드 핸들바도 클라이언트처럼 쓰면 된다. user, verifyEmailUrl, urlHome 을 각각 인자로 사용하는데 이 세개를 handlebars에서도 똑같이 이용한다.

<div>
<img src="{{urlHome}}image/header.img">
</div>
<p>
  안녕하세요. {{user}}님. 가입을 환영합니다.
</p>
<p>
  사용자 확인을 위해 아래 링크를 클릭해주세요.
</p>
<p>
  <a href="{{verifyEmailUrl}}" target="_blank">{{verifyEmailUrl}}</a>
</p>

이렇게 구성하면 오케이.
아마 verifyEmailUrl 은 아마도 http://localhost:3000/#/verify-email/HmfLCxjRKoRreqKWW 이런 형태일텐데

이제 이 경로로 오면 실제로 처리해주는 걸 만들어주면 되겠다.
클라이언트로 /verify-email/<token> 의 URL요청이 들어오면 처리하면 될텐데
내 경우엔 meteor-router(https://github.com/tmeasday/meteor-router) 패키지를 사용하므로
아래와 같이 구현했다.

Meteor.Router.add
....
  '/verify-email/:token':(token)->
    result = Meteor.call 'confirmEmail', token
    'confirmMail'

token을 인자로 'confirmEmail' method를 호출하면 해당계정의 email의 verified가 true가 되면서 사용가능한 상태로 만들 수 있다. 그리고 confirmMail 템플릿으로 포워딩하는 것으로 마무리.
만일 싱글페이지 앱이라면 url 분석해서 처리하는 방법도 있겠다.

물론 인증이 필요한 부분에 필터링을 한다던가 하는 세세한 부분의 구현이 있지만 실제로 해보면서 확인해보자.

2013년 10월 4일 금요일

Could not locate package.js 오류가 발생하면서 mrt/meteor 가 실행 안될때

/packages 디렉토리를 죄다 .gitignore로 등록해서 온 프로젝트가 있어서
지우고 다시 했더니 Could not locate package.js .. 오류가 발생했다.

https://github.com/DiscoverMeteor/Microscope/issues/11

이 글을 참조.

mrt uninstall --system

하고 다시 실행했더니 잘된다. 다행이네 다행이야.

2013년 10월 3일 목요일

node.js에서 angular.js 같은 동적 웹페이지를 사용할 때 crawling 문제

이전글 (http://spectrumdig.blogspot.kr/2013/10/spiderable-package.html)에서 meteor가 어떻게 봇들에게 렌더링된 이후의 html을 전달하는지 보았다.

근데 만일 node.js에서 같은 구현을 해야한다면?
역시 request를 까발려서 _escaped_fragment_ 가 있는지 user-agent가 어떤 것인지 분별하면 된다.

방법이야 여러가지가 있을 텐데
preprocessor를 사용하여 가로채서 조건에 맞으면 처리하고 아닐 경우 next()로 포워딩하는 방법이 있고 (https://github.com/acidsound/pushpot/blob/master/app.js#L30)

Express/Connect Middle-ware를 사용하여

app.use(function(req, res, next){
  if (req.query._escaped_fragment_ ||
    [/^facebookexternalhit/i, /^linkedinbot/i, /^twitterbot/i].some(function(v) {
      return v.test(req.headers['user-agent']);
    })) {
      // render by phantomJS
      res.send(getHTMLfromPhantomJS(req.url));
  } else {
    next();
  }

이런 식으로 처리해주면 되겠다.발로 짜서 실제 작동 여부는 모르겠다.
getHTMLfromPhantomJS 는 물론 직접 구현하시라 :p
https://github.com/meteor/meteor/blob/devel/packages/spiderable/spiderable.js 이런식으로 구현하면 됨.

spiderable package를 살펴보았다.

동적페이지를 만들면 웹크롤러들이 찌르러 왔다가 빈손으로 돌아가곤 한다.
이래서야 안될일.

발번역부터 시작해보자.
// list of bot user agents that we want to serve statically, but do
// not obey the _escaped_fragment_ protocol. The page is served
// statically to any client whos user agent matches any of these
// regexps. Users may modify this array.
//
// An original goal with the spiderable package was to avoid doing
// user-agent based tests. But the reality is not enough bots support
// the _escaped_fragment_ protocol, so we need to hardcode a list
// here. I shed a silent tear.
우리가 정적으로 제공하고 싶지만 _escaped_fragment_ 프로토콜을 따르지 않는 봇 사용자 에이전트의 목록입니다.
페이지는 사용자 에이전트가 이러한 정규식 중 하나와 일치하는 모든 클라이언트에 정적으로 제공합니다. 사용자는 이 배열을 변경할 수 있습니다.
spiderable 패키지와 원래 목표는 사용자 에이전트 기반의 테스트를 실시하는 것을 피하기 위해서였다.
하지만 현실은 봇들은 _escaped_fragment_ 프로토콜을 지원하지 않기 때문에, 여기에서는 목록을 하드 코딩해야합니다. 나는 조용히 눈물을 흘렸다.
Spiderable.userAgentRegExps = [
    /^facebookexternalhit/i, /^linkedinbot/i, /^twitterbot/i];

요놈들! 말을 듣지 않는 나쁜 아이들이 니 놈들이렸다! 이 오라질 것들!
아래와 같이 javascript 코드를 생성하여 phantom.js에 밀어 넣는다. 파일로에서 넣지 않고 바로 생성하기 위해 /dev/stdin 을 사용하여 밀어넣는다. 아마 이것 때문에 윈도우에선 애로사항이 꽃필 것이다. 방법이야 있지만 패스.

    var phantomScript = "var url = " + JSON.stringify(url) + ";" +
          "var page = require('webpage').create();" +
          "page.open(url);" +
          "setInterval(function() {" +
          "  var ready = page.evaluate(function () {" +
          "    if (typeof Meteor !== 'undefined' " +
          "        && typeof(Meteor.status) !== 'undefined' " +
          "        && Meteor.status().connected) {" +
          "      Deps.flush();" +
          "      return DDP._allSubscriptionsReady();" +
          "    }" +
          "    return false;" +
          "  });" +
          "  if (ready) {" +
          "    var out = page.content;" +
          "    out = out.replace(/<script[^>]+>(.|\\n|\\r)*?<\\/script\\s*>/ig, '');" +
          "    out = out.replace('<meta name=\"fragment\" content=\"!\">', '');" +
          "    console.log(out);" +
          "    phantom.exit();" +
          "  }" +
          "}, 100);\n";

소스 코드의 주 내용은 별게 없다. url을 인자로 받은 페이지를 만들어 반환하되 페이지 렌더링이 끝나는 시간을 가정하여 0.1초마다 계속 Meteor 어플리케이션의 존재를 확인하면 Deps를 flush하고 모든 subscription 들을 완료하였는지를 ready로 넘겨준다. 마지막으로 ready가 참이면 page.content를 넘겨주면서 끝난다.

단, <script>덩어리들을 홀랑 날리고 <meta name="fragment" content="!"> 도 홀랑홀랑 날린 다음에 stdout으로 보내주는 것이다. 평범한 html 문서 조각이 되는 것이다.

자. 큰덩어리로 돌아가는 구조를 말로 한번 풀어보자면 이런 것이다.

WebApp.connectHandlers 를 통해 들어온 놈들 중
request url이 _escaped_fragment_= 를 포함하거나  user-agent 이름이 facebookexternalhit, linkedinbot, twitterbot 같은 걸로 들어올 때
phantomJS를 가동해서 page.content를 얻은 다음 페이지로딩이 완료된 다음에 <script>와 <meta name="fragment" content="!"> 을 제거해서 돌려준다...
google 같은 경우 <meta name="fragment" content="!">가 있는 경우 _escaped_fragment_=를 붙여서 요청하는데 이때 phantomJS로 렌더링한 페이지 본문을 넘겨준다.

...라는 아름다운 이야기가 되겠다.

이래도 뭔소린지 모르겠다면 진리의 evetedmind 동영상을 보자.
https://www.eventedmind.com/posts/meteor-the-spiderable-package

재밌는 건 이 링크도 역시 spiderable 적용이다.
curl https://www.eventedmind.com/posts/meteor-the-spiderable-package
한 결과랑

curl https://www.eventedmind.com/posts/meteor-the-spiderable-package?_escaped_fragment_=
한 결과를 비교해보면 한눈에 알 수 있다.

마찬가지로 다른 bot들을 적용해보려면 user-agent를 주면 된다.

curl -s -A "facebookexternalhit" https://www.eventedmind.com/posts/meteor-the-spiderable-package

훌륭하지 않은가?