Hatena::ブログ(Diary)

ursmの日記

2013-02-28

移転

そういえばブログを移転していたのでした。

http://ursm.jp/

引き続きよろしくお願いいたします。

2011-06-25

Linux 用の pow みたいなもの、hoof

https://github.com/pyromaniac/hoof

pow みたいに http://#{app_name}.dev で Rack アプリケーションにアクセスできるようになります。

どうやってるのかなと見てみたら、名前解決にカスタム NSS モジュール、ポートの振り替えに iptables を使っているようです。アプリケーションサーバは普通の Unicorn です。

インストール

hoof はポート 80/443 へのアクセスを振り替えるために iptables の REDIRECT target を使うのですが、私の環境では有効になっていませんでした。カーネル設定を変更して make && make install modules_install して reboot します。

Linux/x86_64 2.6.39-gentoo-r1 Kernel Configuration

[*] Networking support  --->
      Networking options  --->
        [*] Network packet filtering framework (Netfilter)  --->
              [*] Advanced netfilter configuration
                  IP: Netfilter Configuration  --->
                    <*>   Full NAT
                    <*>     REDIRECT target support

iptables を有効にします。

# /etc/init.d/iptables save
 * Saving iptables state ...
# eselect rc add iptables
Adding iptables to following runlevels
  default                   [done]
# rc
 * Loading iptables state and starting firewall ...

あとは README の通りです。

$ gem install hoof
Successfully installed hoof-0.0.7
$ hoof install
gcc -Wall -fPIC -c -o nss_hoof.o nss_hoof.c -O2
gcc -shared -o libnss_hoof.so.2 nss_hoof.o -Wl,-soname,libnss_hoof.so.2 -O2
ln -sf /home/ursm/.rvm/gems/ruby-1.9.2-p180/gems/hoof-0.0.7/ext/libnss_hoof.so.2 /lib
Successfully installed NSS library
Successfully added iptables rules
$ sudo vim /etc/nsswitch.conf
(hosts: に hoof を足す)
# grep hoof /etc/nsswitch.conf 
hosts:       files dns hoof

使い方

$ rails new app && cd app
$ hoof init
      create  ome/ursm/.hoof/app
      append  Gemfile
$ bundle

~/.hoof 以下にシンボリックリンクが作成されて、Gemfile に unicorn が足されます。

$ hoof start
hoof: process with pid 4279 started.

サーバの起動は手動です。pow と違ってアクセス時に自動起動したりはしません。

これでめでたく http://app.dev でアプリケーションにアクセスできます…が、Chromium と hoof の NSS モジュールの相性が悪いようでガンガン落ちます。Firefox なら大丈夫でした。

2010-11-16

RubyConf 2010

ルイジアナ州ニューオリンズで開催された RubyConf 2010 に行ってきました。私の英語力は残念すぎるので、テクニカルなセッションを選んで前の方に陣取りもっぱらスライドを読んでいました。こんなのでも結構楽しめてしまうあたり RubyConf は凄い。

やはり実際に参加してみると色々な気づきがありました。例えばテスティングについて、クリーンなコードについて、コミュニティなるものについて。ここ最近抱えているいくつかの悩みに対して次に進むべき方向が見出せたように思います。

何かについて考えを深めたいとき、手っ取り早い方法は別の視点から三角測量することです。日本の Rubyist にとって RubyConf は「別の視点」を得られる最適な場と言えるでしょう。だからみんな一度は行くといいと思うよ。

2010-09-25

ursm.jp に蛍機能が実装されました

http://ursm.jp

ChromeSafari 5 でアクセスすると他の人のマウスカーソルがぼんやり光って見えます。誰もいなかったら複数ウィンドウで試してみてください。

ソースコードはこちら: http://github.com/ursm/ursm.jp

node.js を試してみた

凄いという噂は耳にするものの、何なのかよくわからない node.js を試してみました。

イベント駆動かつ非同期 I/O なアーキテクチャのサーバサイド JavaScript 処理系で、リソースの消費を抑えつつも高いパフォーマンスを実現するものと理解しましたがあってるんでしょうか。

インストール

monoid/gentoo-nodejs ? GitHub

私は Gentoo 使いなので、ここの overlay から emerge しました。

% layman -f -o http://github.com/downloads/monoid/gentoo-nodejs/nodejs-layman.xml -a nodejs
% emerge -av nodejs

hello, world

公式サイトのサンプルをそのまま写経します。

// example.js
var http = require('http');
http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('Hello World\n');
}).listen(8124, "127.0.0.1");
console.log('Server running at http://127.0.0.1:8124/');
% node example.js 
Server running at http://127.0.0.1:8124/
% curl localhost:8124
Hello World

Rack を生で使ってる感じですね。

npm (Node Package Manager)

RubyGems みたいなやつです。

まず ~/.npmrc を作成します。

root = /home/ursm/.npm/libraries
binroot = /home/ursm/.npm/bin
manroot = /home/ursm/.npm/man

.bashrc などで環境変数を設定します。

export NODE_PATH=/home/ursm/.npm/libraries:$NODE_PATH
export PATH=/home/ursm/.npm/bin:$PATH
export MANPATH=/home/ursm/.npm/man:$MANPATH

インストールスクリプトを走らせます。

% curl http://npmjs.org/install.sh | sh
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   338  100   338    0     0    502      0 --:--:-- --:--:-- --:--:--  1640
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  568k  100  568k    0     0   186k      0  0:00:03  0:00:03 --:--:--  400k
node cli.js cache clean
npm info it worked if it ends with ok
npm info version 0.2.2
npm ok
node cli.js rm npm
npm info it worked if it ends with ok
npm info version 0.2.2
npm info not installed npm
npm ok
node cli.js install npm
npm info it worked if it ends with ok
npm info version 0.2.2
npm info fetch http://registry.npmjs.org/npm/-/npm@0.2.2.tgz
npm info install npm@0.2.2
npm info activate npm@0.2.2
npm info build Success: npm@0.2.2
npm ok
It worked

入りました。node.js 界隈はインストール方法が curl ... | sh のものが多くて怖いです。

この npm というやつ、man を見てもコマンド一覧すら載っていません。とりあえずパッケージのインストールは npm install のようです。

% npm install express
npm info it worked if it ends with ok
npm info version 0.2.2
npm info fetch http://registry.npmjs.org/express/-/express-1.0.0rc3.tgz
npm info install express@1.0.0rc3
npm info activate express@1.0.0rc3
npm info build Success: express@1.0.0rc3
npm ok

npm ls で全パッケージの一覧を表示、検索はまだないみたいですnpm ls pkgname でパッケージ名から検索できます。

CoffeeScript

いきなり脇道に入りますが、JavaScript の文法ってどうも好きになれません。括弧を省略できないのとセミコロンと function() が長すぎるのが駄目みたいです。

Ruby/Python っぽい記法から JavaScript を生成する CoffeeScript というものを試してみます。

% npm install coffee-script

先ほどのサンプルを CoffeeScript に書き直してみます。

# example.coffee
http = require('http')
http.createServer (req, res) ->
  res.writeHead 200, 'Content-Type': 'text/plain'
  res.end 'Hello World\n'
.listen 8124, "127.0.0.1"
console.log 'Server running at http://127.0.0.1:8124/'

こんなふうに変換されます。

% coffee -p example.coffee 
(function() {
  var http;
  http = require('http');
  http.createServer(function(req, res) {
    res.writeHead(200, {
      'Content-Type': 'text/plain'
    });
    return res.end('Hello World\n');
  }).listen(8124, "127.0.0.1");
  console.log('Server running at http://127.0.0.1:8124/');
}).call(this);

coffee コマンドで直接実行できます。

% coffee example.coffee
Server running at http://127.0.0.1:8124/

文字列中の式展開や可変長引数のサポートもあっていい感じです。

Express

Express というフレームワークを使ってみます。Sinatra によく似ています。

% npm install express

Express は Connect というフレームワークに依存しています。Connect は Rack middleware のような仕組みを実現するためのものみたいです。

express = require('express')
app = express.createServer()

app.get '/', (req, res) ->
  res.send 'Hello World\n'

app.listen 8124

素晴らしいことに Haml が使えます。hamlhamljs があって紛らわしいのですが、Express がデフォルトで対応しているのは haml の方です。

% npm install haml
express = require('express')
app = express.createServer()

app.configure ->
  app.set 'view engine', 'haml'

app.get '/', (req, res) ->
  res.render 'index', layout: false, locals:
    message: 'hello, world'

app.listen 8124
-# views/index.haml

%h1 hello
%p&= message

layout: false にしないと layout.haml がないときエラーになります。いけてませんね。locals はあるものの、インスタンス変数に相当するものがないので毎回すべての変数を渡さないといけません。content_for がないのもかなり痛いです。

この Haml あまり出来がよくなくて、コメントがコメントとして機能していません。後で hamljs を試してみます。

Spark

listen するポートがコードにベタ書きしてあるのは嫌ですね。rackup みたいなものはないかと探していたら、ありました。

% npm install spark

spark で起動できるようにするには「app.jshttp.Server または net.Server のインスタンスをエクスポート」すればいいらしいです。

つまりこういうことみたいです。

# app.coffee
express = require('express')
app = module.exports = express.createServer()

app.configure ->
  app.set 'view engine', 'haml'

app.get '/', (req, res) ->
  res.render 'index', layout: false, locals:
    message: 'hello, world'

あるファイルが require() されたとき、module.exports に設定されたオブジェクトが返ります。これは CommonJS という仕様の仕組みだそうです (参考: Server-side JavaScript と CommonJS - 電脳戦士ハラキリ -SE道とは死ぬ事と見つけたり-)。

このファイルは .js じゃなくて .coffee なので、spark で起動するにはちょっと小細工しないといけません。

% spark app.coffee --eval "require('coffee-script')"
Spark server(9596) listening on http://*:3000 in development mode

require('coffee-script') すると require().coffee を扱えるよう拡張されます。

spark を経由するとポート番号やワーカ数を設定できるようになります。

% spark -h
Usage: spark [options]

Options:
  --comment            Ignored, this is to label the process in htop
  -H, --host ADDR      Host address, defaults to INADDR_ANY
  -p, --port NUM       Port number, defaults to 3000
  --ssl-key PATH       SSL key file
  --ssl-crt PATH       SSL certificate file
  -n, --workers NUM    Number of worker processes to spawn
  -I, --include PATH   Unshift the given path to require.paths
  -E, --env NAME       Set environment, defaults to "development"
  -M, --mode NAME      Alias of -E, --env
  -e, --eval CODE      Evaluate the given string
  -C, --chdir PATH     Change to the given path
  -c, --config PATH    Load configuration module
  -u, --user ID|NAME   Change user with setuid()
  -g, --group ID|NAME  Change group with setgid()
  -v, --verbose        Enable verbose output
  -V, --version        Output spark version
  -K, --no-color       Suppress colored terminal output
  -h, --help           Outputy help information
  --ENV VAL            Sets the given spark environment variable

ただ残念なことに、先ほどの CoffeeScript 対応ハックを使うと複数ワーカの起動に失敗してしまいます。おそらく spawn したプロセスで --eval の内容が実行されていないのだと思いますが、直せそうだったら直してみたいものです。

長くなってきたので続きはまた今度。

2010-09-22

デプロイ時に releases を自動的に掃除する

% cap -e deploy:cleanup
------------------------------------------------------------
cap deploy:cleanup
------------------------------------------------------------
Clean up old releases. By default, the last 5 releases are kept on each server
(though you can change this with the keep_releases variable). All other deployed
revisions are removed from the servers. By default, this will use sudo to clean
up the old releases, but if sudo is not available for your environment, set the
:use_sudo variable to false instead.

というわけなのですが、こんなの手動で実行するほど勤勉じゃないので hook を仕掛けます。

# config/deploy.rb
after 'deploy:update', 'deploy:cleanup'

こうするとデプロイのたびに古い release が削除されて嬉しいというお話でした。

Tailable Cursors を試してみた

http://www.mongodb.org/display/DOCS/Tailable+Cursors

tail -f みたいなカーソル」だそうな。

Tailable Cursor が使えるのは Capped Collection だけらしい。Capped Collection はサイズに上限があるコレクションで、上限に達すると古いドキュメントから消えていく。挿入順で読み出せることが保証されている (普通のコレクションは保証されない)。

まず Capped Collection を作る。MongoDB だと明示的にコレクションを作る場面はほとんどないんだけど、Capped Collection を作るときは必要になる。

>> require 'mongo'
>> col = Mongo::Connection.new.db('test').create_collection('capped', :capped => true, :size => 10000)

Tailable Cursor を作る。

>> cur = Mongo::Cursor.new(col, :tailable => true)

適当に書いたり読んだりしてみる。

>> col.insert(:i => 1)
>> col.insert(:i => 2)
>> cur.next_document
=> {"_id"=>BSON::ObjectId('4c98da42ad7d212878000001'), "i"=>1}
>> cur.next_document
=> {"_id"=>BSON::ObjectId('4c98da45ad7d212878000002'), "i"=>2}
>> cur.next_document
=> nil
>> col.insert(:i => 3)
>> cur.next_document
=> {"_id"=>BSON::ObjectId('4c98da53ad7d212878000003'), "i"=>3}

カーソルがコレクションの終端に達しても、新しいドキュメントが挿入されると続けて読めるということみたい。終端で next_document したときにブロックしてくれればキューとして使えそうなんだけどな。