静岡でNode.jsのハンズオンを開催します

最近完全にHaskellにシフトしてる感がありますが、明後日(6/30)の土曜日に静岡でNode.jsハンズオンをやります

キャンセルが出てて若干空きがあるみたいなんで、時間があれば参加するといいと思います。

スライドつくったのでGithubにpushしておきました。

git clone git@github.com:kzfm/shizdraw.git
cd shizdraw/slides
python -m SimpleHTTPServer
# http://localhost:8000/で立ち上がる

でスライドを見ることができます。ちなみにコードはCoffeeScriptで書いてます。

mochaでBDD

ちょっと前にTest Driven Development with a Node.js TCP Server?っていう質問を見つけて、コメントにmocha使えやって書いてあったのでmochaが気になっていた。

ちょっとドキュメントみたんだけど、jasmineみただなぁと。それからいまいち良さが伝わってこない。vowsでいいじゃんと。

もうちょっと探したら、同じようにvowsから移行しようかなぁっていう質問もみつけた。

Should I switch from Vows to Mocha?

done callbackがいいらしいが。

なにかで使ってみないとわからんなぁ。

ProductName テスト駆動JavaScript
Christian Johansen
アスキー・メディアワークス / ?円 ( 2011-11-25 )


Express+now.js+coffeescriptでつくるお絵描きwebサービスをherokuにデプロイ

今月末にやるハンズオンのサンプルを作りました。興味があったら是非参加してください。そしてNode.jsについて熱く語り合いましょう、さらにその勢いでHaskell+yesodを誰か僕に教えてください。

どんなものをつくるのかはherokuにデプロイしておいたので参考にしてください。コードはGithubです。

テストのために今朝ちょいとtwitterで「書いてみて」ってお願いしたら、こんなにたくさんの画伯が描いてくれました。感謝。

shizdraw

さて、ハンズオンの流れ的には、

  • @k0sukeyが、node.jsインストール、hello worldからherokuデプロイとsoket.ioの触りまでっていう導入的な内容
  • @ksmakotoのwebsocketとnode.jsのHTTPサーバーなんかを低レイヤー寄りのスタンスで書いていくっていう内容
  • 僕が、抽象度の高いライブラリを使ってウェブサービスを作ってみるっていう内容。私のサンプルでは通信部分が3行くらいに収まっています(now.js最高ですね!)。

あとは、サーバーサイドな内容(EventEmitterまわりかな?)が一本入る予定になっています。楽しみですね。

次の日には浜松で浜松 Titanium Mobile 勉強会があるので、土曜はしぞーかおでんを(イベントドリブンで)食べつつ、日曜は浜松でひつまぶしつつTiっていう小旅行勉強会的な何かもいいんじゃないかなー

浜松行きたいんだけどなー

Flask+jqmでtweetできるモバイルサイトを作ったのとNode.jsハンズオンについて

昨日の三島バルのために自分用モバイルサイトをつくっておいたりとか。そもそもバルで酒ののみながらハッシュタグツイートって難易度高すぎですよね。そこを適当にボタン押せばいいだけにしときたいなぁと。それからバルイベントなので位置情報くっつけるべきでしょうとか思うんだけどAPI通さないといけないっぽいのでそこら辺面倒臭かった。

1336864962

Flask+pymongo+tweepy+jQuery Mobileでモバイルサイトをつくってsupervisorで死活監視しながらgunicornで動かしてみたのと、now.jsとgooglemapを使ったリアルタイムなやつをつくっておいた。

で、一緒のサーバーで動かしたかったのでこんな構成です。

ProductName jQuery Mobile
Jon Reid
オライリージャパン / 1995円 ( 2011-12-22 )


Node.jsハンズオンの宣伝

now.jsとgooglemapを使ったリアルタイムのやつは、位置情報付きのツイートがなくて残念だったので、日本の位置情報を拾うのに変えておいた。

眺めてるとわかるんだけど、デリヘル関係多いですね。なるほどなぁとは思うんだけど。

というわけで、来月の30日に静岡でNode.jsの勉強会があります。私はこれみたいなサイトをつくるハンズオンをやるので興味があれば参加すると良いと思います。

さくらのVPS512でWebSocket対応のバーチャルホストを動かす

さくらのVPS 2Gに移行したので、GW最終日は余ったVPSで遊んでいる。

ちょっと下のような構成にしたくてウェブサーバーをApacheかNginxで悩んだんだが、さくらのVPS512はyumでnginxが入らないしApacheもWebSocketに対応してないしどうしたもんかなぁと。

HTTP_SERVER
├── Express+Now.js
└── Flask+Gunicorn

node-http-proxyを使えばいいらしいのでこれをforeverで動かしてみた。

バーチャルホストの書き方がGistにあったので参考にした。

Expressをproduction環境で動かすには環境変数NODE_ENVにproductionをセットすればいいんだけどforeverで使いたい場合はこうすればいいらしい。

twitter streaming apiのlocationで検索する

単に緯度経度の順番ではまった。

それから、富士-三島あたりを指定しているのに山形のほうまで拾ってくるのでちょっとなぁって感じ。

ナウなヤングのnow.js

now.js楽ちんですね。

twitter-streamとnow.jsを使ってリアルタイムにtweetを拾ってブラウザに表示したくなったのでやってみた。コード書いてる時に丁度スケットダンスが放映されていたたみたいで、twitterのトレンドに表示されてたのでこれを拾ってみた。

ちなみにジャンプは毎週読んでるけどスケットダンスはほとんど見ないのでついていけてませんな。

twst

スケルトン作成

とりあえずいつものようにコマンドを打って、スケルトンを作成

express twnow
cd twnow

package.jsonを修正して

{
    "name": "application-name"
  , "version": "0.0.1"
  , "private": true
  , "dependencies": {
      "express": "2.5.8"
    , "jade": ">= 0.25.0"
    , "ntwitter": ">= 0.3.0"
    , "now": ">= 0.8.1"
  }
}

依存モジュールをインストールする

npm install -d

コード

あとはコードを描く

app.coffee

twitter = require("ntwitter")
express = require("express")
routes = require("./routes")
nowjs = require("now")

twit = new twitter(
  consumer_key: "####"
  consumer_secret: "####"
  access_token_key: "####"
  access_token_secret: "###"
)

app = module.exports = express.createServer()
app.configure ->
  app.set "views", __dirname + "/views"
  app.set "view engine", "jade"
  app.use express.bodyParser()
  app.use express.methodOverride()
  app.use express.cookieParser()
  app.use express.session(secret: "tweetandsweet")
  app.use app.router
  app.use express.static(__dirname + "/public")

app.configure "development", ->
  app.use express.errorHandler(
    dumpExceptions: true
    showStack: true
  )

app.configure "production", ->
  app.use express.errorHandler()

everyone = nowjs.initialize(app)

app.get "/", (req, res) ->
  console.log req.session.user_profile
  res.render "index",
    title: "twitter streaming and now.js"

twit.stream(
  'statuses/filter'
  {track:['#sketdance']}
  (stream) ->
    stream.on 'data', (data) ->
      everyone.now.receiveMessage data
      console.log data
)

app.listen 3000
console.log(
  "Express server listening on port %d in %s mode"
  app.address().port
  app.settings.env
)

style.css

body {
  padding: 50px;
  font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
}

a {
  color: #00B7FF;
}

li.tweet {
  list-style-type:none;
  width: 500px;
}

img.tweet {
  float: left;
  padding-right: 20px;
}

hr {
  width: 500px;
  clear: left;
  border: 0;
  height: 1px;
  background-image: -webkit-linear-gradient(left, rgba(0,0,0,0), rgba(0,0,0,0.75), rgba(0,0,0,0));
  background-image:    -moz-linear-gradient(left, rgba(0,0,0,0), rgba(0,0,0,0.75), rgba(0,0,0,0));
  background-image:     -ms-linear-gradient(left, rgba(0,0,0,0), rgba(0,0,0,0.75), rgba(0,0,0,0));
  background-image:      -o-linear-gradient(left, rgba(0,0,0,0), rgba(0,0,0,0.75), rgba(0,0,0,0));
}

layout.jade

!!!
html
  head
    title= title
    link(rel='stylesheet', href='/stylesheets/style.css')
    script(type="text/javascript", src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js")
    script(src="/nowjs/now.js")
  body!= body

index.jade

h1= title
p sample apps
ul#messages
script
  $(document).ready(function() {
    now.receiveMessage = function(data) {
      $("#messages").prepend(
   '<li class="tweet"><img class="tweet" src=\"' + data.user.profile_image_url +'\">'+ data.text + '<hr></li>');
    }
  });

now.jsは抽象度が上がりすぎてて、裏で何が行われているかよく理解できないんだが、使うぶんには簡単ですね。IE7対策をしなくてもとりあえず動くので、それもお気に入りポイント(企業のブラウザはIEでしかも古いからねー)

ProductName 徹底解説 HTML5 APIガイドブック コミュニケーション系API編
小松 健作
秀和システム / 2730円 ( 2010-12 )


Brunchでtwitter検索

Brunch(Backbone.js)の練習を兼ねて、Backbone.js で HTML の View と Model を分離してみるよのサンプルをBrunchで。

まずは、スケルトン作成

$ brunch new brtwitter

watchしつつ、ファイル更新を検知してコンパイルしつつ、サーバーを3333番portで起動する

$ cd brtwitter
$ brunch watch --server
[19:23:17]: [Brunch]: application starting on http://0.0.0.0:3333.
[19:23:17]: [Brunch]: compiled.

あとはapp以下のファイルをいじる。

models/tweet.coffee

モデルをつくる。

class exports.Tweet extends Backbone.Model
  initialize: ->
    @set
      domId: "tweet_#{@cid}"

collections/tweet_list.coffee

コレクションを設定。ここまでは簡単

{Tweet} = require 'models/tweet'

class exports.TweetList extends Backbone.Collection
  model: Tweet

views/tweet_list_view.coffee

{TweetView} = require 'views/tweet_view'
{Tweet} = require 'models/tweet'
tweetListTemplate = require './templates/tweet_list'

class exports.TweetListView extends Backbone.View
  id: 'tweets-view'

  initialize: ->
    app.tweetList.bind 'add', @addOne
    $('body').append @render().el

  render: ->
    $(@el).html tweetListTemplate()
    @

  addOne: (tweet) ->
    view = new TweetView model: new Tweet(tweet)
    $(@el).find('#tweets').prepend view.render().el

views/templates/tweet_list.eco

テンプレートは今回ecoをデフォルトのecoを使った。個人的にはjadeのほうが好きなのでそのうちJadeに乗り換える。

views/templates/tweet_list.eco

<ul id="tweets"></ul>

views/tweet_view.coffee

同様にtweetも

tweetTemplate = require('./templates/tweet')
class exports.TweetView extends Backbone.View
  id: 'tweet-view'
  tagName: 'li'

  initialize: ->
    @model.view = this

  render: ->
    $(@el).html tweetTemplate tweet: @model.toJSON()
    @

views/templates/tweet.eco

<div class="twtr-avatar">
  <div class="twtr-img">
    <a href="http://twitter.com/intent/user?screen_name=<%= @tweet.from_user %>" target="_blank">
      <img src="<%= @tweet.profile_image_url %>">
    </a>
    <p><%= @tweet.text %></p>
  </div>
</div>

assets/index.html

bodyタグの間に入れとく

<input id="searchTxt" type="text" value="backbone.js" />
<button id="searchBtn">search</button>
<div id="tweetContainer">searchボタンを押すとTwitterから検索してくるよ!</div>

initialize.coffee

最後に初期化する。

{BrunchApplication} = require 'helpers'
{MainRouter} = require 'routers/main_router'
{TweetList} = require 'collections/tweet_list'
{TweetListView} = require 'views/tweet_list_view'

class exports.Application extends BrunchApplication
  initialize: ->
    @router = new MainRouter
    @tweetList = new TweetList
    @tweetListView = new TweetListView

    $("#searchBtn").click (e) ->
      jqxhr = $.ajax
        dataType: "jsonp"
        url: "http://search.twitter.com/search.json"
        data: { q: encodeURI( $( "#searchTxt" ).val() ) }
        jsonp: "callback"

      jqxhr.done (data) ->
        _.each data.results, (result) ->
          app.tweetListView.addOne(result)

window.app = new exports.Application

ファイル構成はこんな感じ。home_*は必要ないけどスケルトンで作成されたのでそのまま放ってある。

.
├── assets
│   ├── images
│   └── index.html
├── collections
│   └── tweet_list.coffee
├── helpers.coffee
├── initialize.coffee
├── models
│   └── tweet.coffee
├── routers
│   └── main_router.coffee
├── styles
│   └── main.styl
└── views
    ├── home_view.coffee
    ├── templates
    │   ├── home.eco
    │   ├── tweet.eco
    │   └── tweet_list.eco
    ├── tweet_list_view.coffee
    └── tweet_view.coffee

backbone.jsってviewがファットになんのね。

Ace - Sinatra for Node

Spineの作者製なのでCoffeeScriptで書けていい感じっぽいんだけど、Expressと比べてどうなんだろうか。 少し触ってみないとそこらへんがよくわからん。

この本は面白かった。

ProductName JavaScript Web Applications
Alex Maccaw
Oreilly & Associates Inc / 3020円 ( 2011-08-30 )


ちなみに今はExpress+nowjsっていう組み合わせでイントラサイトを作っているけど、調子いいのでなかなか気に入っている。

再来月に二回目の三島バルがあるらしいので、それまでにつくる会が開ければExpressかAceでこんなの作ってみようかなぁと思っている(自分のために)。多分俺のバルっていうタイトルのサイトで、酒を飲んでふらふらしながら自分探しの旅をするっていう設定のリアルタイムウェブなサイトにしようかなぁと。

Node.jsのalwaysモジュールを見てcoffeeにはwatchオプションがあるよなと思った。

node.jsの開発時に。ファイル変更でサーバプロセスを自動再起動「Always」を読んで、Expressの開発に使えるかも!?と思ったが、よくよく考えたら自分はいつもcoffeescriptで書いてるんだった。

あとcoffeeコマンドには--watchってオプションがあるからモジュールに頼らなくてもいいのだよなぁ。

ExpressでOpenID認証

イントラにOpenIDを入れたので認証のことを考えなくてもよくなって快適なので、色々作っている。

ちょっとsocket.ioを使いたかったので、Expressでopenid認証させるサンプルを書いてみた。node-openidを使っている。openid.ne.jpのID使って試してある。mixi-openidは動かなかったし、facebookとgoogleのopenidはドキュメントの場所がよくわからなくてイラッと来てやめた。

app.coffee

authenticateっていうパスにアクセスするとopen-id認証してverifyにリダイレクトされてくるようになっている。

express = require("express")
routes = require("./routes")
openid = require('openid')
url = require('url')
querystring = require('querystring')
relyingParty = new openid.RelyingParty(
  'http://localhost:3000/verify',
  null,
  false,
  false,[])

app = module.exports = express.createServer()
app.configure ->
  app.set "views", __dirname + "/views"
  app.set "view engine", "jade"
  app.use express.bodyParser()
  app.use express.methodOverride()
  app.use app.router
  app.use express.static(__dirname + "/public")

app.configure "development", ->
  app.use express.errorHandler(
    dumpExceptions: true
    showStack: true
  )

app.configure "production", ->
  app.use express.errorHandler()

app.get "/", routes.index

app.get "/authenticate", (req, res) ->
  identifier = req.query.openid_identifier
  relyingParty.authenticate identifier, false, (error, authUrl) ->
    if error
      res.send "Authentication failed: " + error
    else unless authUrl
      res.writeHead 200
      res.send "Authentication failed"
    else
      res.redirect(authUrl);

app.get "/verify", (req, res) ->
  relyingParty.verifyAssertion req, (error, result) ->
    if not error and result.authenticated
      res.send 'Success :)'
    else
      res.send 'Failure :('

app.listen 3000
console.log "Express server listening on port %d in %s mode",
  app.address().port, app.settings.env

view/index.jade

formを追加しただけ

h1= title
p Welcome to #{title}

form(method="get", action="/authenticate")
  p Login using OpenID
  input(name="openid_identifier")
  input(type="submit", value="Login")

イラッと来るのはsnow leopardにあげたらmacがやたらと重くなった部分も多い。さすがに4年も経つのでそろそろ買い換えてもいいかなと思ったりするところではある。