2

SinatraっぽいWAFを作る、46行で

“Tamago”というWeb Application Frameworkを作った。
https://github.com/shokai/tamagoに置いてある。


昨日学校に行く前にメシを食いながらHerokuやSqaleやらPaaSについて調べていたら、SinatraやRailsではなくRackを直接使ってPaaSで動かしている人たちが何人かいた。
sqale使ってみた – komagataとか。


で、電車の中でふとWAFを作ってみたらどうかと思って作ってみた。
最終的に学校に着く頃にこんな風に書けるのができてた。
GETやらPOSTで指定したパスへのアクセスを受け取って、Hamlのテンプレートが使えたりするDSLが使える。

get '/' do
haml :index
end

post '/' do
Time.now.to_s
end

get '/env' do
ENV.keys.sort.map{|k|
"#{k}=#{ENV[k]}"
}.join("\n")
end
まんまSinatraの書き方で、何の面白みもない・・・



■Rackとは?
RubyにはWeb Application Framework(以下WAF)にRails、Sinatra、Merb、Padrinoやら色々なのがある。サーバーもThin、Mongorel、Webrick、Passenger+Apacheとか色々ある。これらのWAFとサーバーのうち、どれとどれを組み合わせても動くようにするために、RackというWAFとサーバーの接続方法が決められている。

すごくおおざっぱに言うと、callというクラスメソッドを持ったクラスを宣言して、call内でRack::Requestを受信してRack::Responseを返すようにするだけでいいらしい。



■Rackアプリ最小構成
これがRackを直接使う最小構成になる。
この例ではTamago.callの中でGETやPOSTの判別とかPathとか判別してHTMLを作ってしまっているが、適当にDSLとか決めたりして書けるように拡張したらWAFと呼んでいいだろう。

tamago.rb
require 'rubygems'
require 'rack'
require 'uri'

class Tamago
def self.call(env)
req = Rack::Request.new(env)
body = case req.request_method
when 'GET'
"<html><body><h1>#{URI.decode req.path_info}</h1></body></html>"
when 'POST'
req.params.keys.map{|k| "#{k}=#{req.params[k]}"}.join("\n")
end
Rack::Response.new { |r|
r.status = 200
r['Content-Type'] = 'text/html;charset=utf-8'
r.write body
}.finish
end
end


rackupの設定ファイルを書く
config.ru
require 'tamago'
run Tamago


起動する
rackup config.ru -p 8080

GETアクセスしたらURLのパスの部分がh1タグで囲まれたページがでてくる。



■Sinatraっぽいのを作る
HTTPリクエストをどう処理するかをDSLで書けて、Hamlのテンプレートが使えるようにする。

まずconfig.ru
require 'rubygems'
require File.dirname(__FILE__)+'/tamago'
require File.dirname(__FILE__)+'/app'

run Tamago::Application

WAF本体はこうなった。
本体をTamago::Applicationクラスにして、Viewを作る部分とかはまた別のクラスに分けた。
tamago.rb
require 'rubygems'
require 'rack'
require 'haml'

class Object
def method_missing(name, *args, &block)
case name
when :get, :post, :head, :delete
path = args[0]
Tamago.procs["[#{name.to_s.upcase}] #{path}"] = block
when :haml
Tamago::View.render args[0]
end
end
end

class Tamago
def self.procs
@@procs ||= Hash.new
end

class View
def self.render(template)
template = case true
when template.kind_of?(File)
template.read
when template.kind_of?(Symbol)
File.open("#{ENV['PWD']}/views/#{template}.haml").read
end
raise ArgumentError, 'Argument must be instance of File, String or Symbol.' unless template.kind_of? String
Haml::Engine.new(template).render
end
end

class Application
def self.call(env)
@request = Rack::Request.new(env)
body = Tamago.procs["[#{@request.request_method}] #{@request.path_info}"].call(self)
Rack::Response.new { |r|
r.status = 200
r['Content-Type'] = 'text/html;charset=utf-8'
r.write body
}.finish
end
end
end
app.rbのgetやpostは、config.ruからrequireされた時点で関数として呼ばれる。Object.method_missingを定義しておいてgetやpostを横取りし、blockの中身をHashに保存しておく。
Tamago.callでRack::Requestが来たら、Hashに保存しておいたblockを実行する。


最後にapp.rb
get '/' do
haml :index
end

post '/' do
Time.now.to_s
end

get '/env' do
ENV.keys.sort.map{|k|
"#{k}=#{ENV[k]}"
}.join("
")
end


起動
rackup config.ru -port 8080


というわけでRack上で簡単なWAFを作ってみた。
この分なら、たぶんRackミドルウェアとかを作るのもそんなに難しくないと思う。今度やってみよう。
Sinatraとかでも、Sinatraで書くよりRackの方をいじったほうがスマートに書ける事もあるだろうし。

0

MacMiniにVirtualBoxでUbuntu12.04インストールした

家のネットブックのUbuntuマシン(作ったgemがLinuxで動くか確かめたりカメラ/マイク/シリアルポートを使うための実験用)を廃止してMacMiniのVirtualBox内で運用することにした。
GUI付きでインストールするが、GUIはSkype Botのためにしか使わない。基本的にMacMini側からsshでログインしてCUIで操作する。


■Ubuntuインストール

それぞれダウンロードしておく。

VirtualBoxをインストールして、起動。
左上の「新規」から仮想ディスクイメージをHDD16GB/メモリ512MBで作った。


「起動」ボタンを押す。

何も入ってないカラのイメージなので、さっきダウンロードしたUbuntuのイメージファイルを選ぶ。
画面の指示に従ってUbuntuをインストールする。全部デフォルト設定で良い。
途中で「HDDを消すか」と言われて怖いが、さっき作った仮想ディスクイメージの事なので気にせず消す。

インストールが終わったら再起動。


左上の「Dashホーム」からTerminalで検索してターミナル起動する。
sudo apt-get update
sudo apt-get upgrade
sudo apt-get install ssh emacs-nox screen build-essential virtualbox-guest-additions
適当に色々インストールしておく。

あと環境設定からUbuntuがスリープしない様に設定する。


■IPを固定する
家の中ではMacMiniは192.168.1.3で運用しているので、VirtualBoxのUbuntuを192.168.1.4にする。
一度Ubuntuの電源を切ってから、VirtualBoxの設定画面から「ネットワークアダプタ2」を追加、「ブリッジアダプタ」に設定する。


Ubuntuを起動してから、 /etc/networking/interfaces を編集
auto lo
iface lo inet loopback

auto eth1
iface eth1 inet static
address 192.168.1.4
netmask 255.255.255.0
gateway 192.168.1.1
dns-nameservers 192.168.1.1


ネットワークを再起動して設定を有効にする
sudo /etc/init.d/networking restart
sudo ifconfig
192.168.1.4になっていた。

Mac側から接続する。
ssh 192.168.1.4

なお、これは固定IPなのでラップトップとかで持ち歩いて外のWiFiに接続する時とかには設定しない方が良い。
iface eth1 inet dhcp
にして下のアドレス指定してる所を全部書かないか、そもそもネットワークアダプタ1だけで十分だな


■Macから仮想マシンを起動する
Macのterminalから起動できる
VBoxManage startvm "Ubuntu" --type gui
VBoxManage startvm "Ubuntu" --type headless

参考:launchdでVirtualBoxの仮想マシンを自動起動 – komagata
Mac用のLaunchDaemons用plistも設定しておけば、Macの電源入れると同時にVirtualBoxを起動できる。


■Skype botを自動起動
とりあえずSkypeをインストールする。Skypeにはログインパスワードを覚えさせておく。
Ubuntuの右上の電源ボタンから「自動起動するアプリケーション」を選んで、
/usr/bin/skype を指定。Ubuntuを再起動してみるとSkypeが起動する。

一度終了して、上のVBoxManageコマンドからheadlessで起動してもSkypeが起動できた。

同様にskype-chat-gateway-linuxも起動できる。


■家の外からVirtualBoxのUbuntuにログイン
mydns.jpで非固定IPの家の回線にドメイン名を振っているので、外からMacMiniにはsshでログインできるようになっている。

外出中は
Macbook Air ---[インターネット]---> MacMini(shokai.org) ---[家のLAN]---> VirtualBox(192.168.1.4)
という風に多段sshする事にした。ポートフォワードで22番以外でVirtualBoxのsshを外に出してもいいけど。


Macbook Airの ~/.ssh/config に追記する
Host vb-ubuntu
HostName 192.168.1.4
ProxyCommand nohup ssh -l `whoami` shokai.org nc %h %p
全て同じユーザ名でログインしているのでwhoamiで取り出す。

ssh vb-ubuntu
で外からでもVirtualBoxに接続できる。

参考:sshの多段接続がものすごい便利な件 | Carpe Diem

1

Juliusの辞書が作れない

こうなってしまう

book.grammar has 4 rules
book.voca has 5 categories and 7 words
---
---
no .dfa or .dict file generated
エラーもでない。
juliusのBBSで事例を探すと”—“の間にエラーが表示されるらしいけど何も書かれない。
mkdfa.plをprintfデバッグしたら、どうやら同じディレクトリにあるmkfaが正常に終了していないらしい。


また、mkfaを直接実行すると
zsh: そのようなファイルやディレクトリはありません: /home/sho/tmp/src/grammar-kit-v4.1/bin/mkfa
になってしまう。もちろんmkfaは存在する。


■手順
を見ながら文法と発音を作る。
本を探す簡単な文法「(書誌名)をください」もしくは「(書誌名)ください」を認識する辞書を作りたい。


book.grammar
S      : NS_B BOOK_ PLEASE NS_E
BOOK_ : BOOK
BOOK_ : BOOK WO
PLEASE : KUDASAI

book.voca
% BOOK
インターフェース i N t a a f e e s u
マンガ m a N g a
雑誌 z a s sh i
% WO
を o
% KUDASAI
ください k u d a s a i
% NS_B
<s> silB
% NS_E
<s> silE

Julius記述文法音声認識実行キットのLinux版をダウンロードしてきて、Ubuntu11.04でtarを展開。
辞書作成する
perl ~/tmp/src/grammar-kit-v4.1/bin/mkdfa.pl book
するとこうなる
/tmpbook.grammar has 4 rules
book.voca has 5 categories and 7 words
---
---
no .dfa or .dict file generated
book.termだけ生成される。

■試したこと
  • 記述文法音声認識実行キットに付いているサンプル辞書は実行できた
  • キットに付いているサンプルのgrammarとvocaファイルから辞書を生成しようとすると、やはり”no .dfa or .dict file generated”になる
  • 文法や発音ファイルをshift_jisやeuc-jpで保存しなおしたが変化なし
  • mkdfa.plをprintfデバッグしたら、mkfaがエラー(1)を返していた
  • mkdfa.plが生成する中間ファイルを使って自分でmkfaを呼び出すと、mkfaが存在しないと言われる
  • zshと相性が悪いのかもしれないので、bashで試しても同様にmkfaが存在しないと言われる

0

mydns.jpのBasic認証でのDNS更新URLが変わった

家のサーバー(MacMini等)に外からアクセスするために、mydns.jpを使っている。

急に家のサーバーに接続できなくなったのでmydns.jpを見たらwebページが新しくなってた。ついでにBasic認証でのDNS更新URLも変わってた

変更前

wget --spider http://username:passwd@www.mydns.jp/login.htm


変更後
wget --spider http://username:passwd@www.mydns.jp/login.html


htmがhtmlになってた。

mydns.jpはDiCEやPOP3受信やHTTP+Basic認証でのDNS更新ができる。
普通のUnix系OSで自宅サーバーするならHTTP+Basic認証が一番楽で、
crontab -e して
*/10 * * * * wget --spider http://username:passwd@www.mydns.jp/login.html > /dev/null 2>&1
とか書いておけば10分毎に更新してくれるので外からでも家のMacが使える。


4年間ぐらい使ってるけど異常が起こったのは今回が初めて。

0

Sinatra+Haml+jQueryテンプレートにDataMapper版とMongoid版を追加した

去年作ったSinatra+Haml+jQuery入門のテンプレ、なにげにしょっちゅうアップデートしている。
内容は↑にも書いてあるがおみくじを引くだけの超単純なアプリだ。
新規プロジェクトを開始する時はコレをcloneして使うので、余計な物を付けない様にしている。

githubのリポジトリはここ https://github.com/shokai/sinatra-template


■使っている部品
今はこういう構成になっている

  • Ruby 1.8.7
  • Sinatra 1.3
  • Haml + sinatra-content-for
  • Sass
  • jQuery
  • foreman
  • bundler


master以外のブランチにmongoiddm-mysqlの2つがある。
それぞれDataMapper+MySQLと、Mongoid+MongoDBをバックエンドにしたやつ。


DataMapper+MySQL版はこんなファイル構成にしている。(Mongoidもほぼ同じ)
.
├── Gemfile # 必要なGEMを書いておくファイル。bundle installで一気にインストールされる。
├── Procfile # webサーバーと同時に起動するプロセスを書いておく
├── README.md
├── bin
│   ├── console.rb # DB接続してModel読み込んだ状態でIRBが起動する
│   ├── db_migrate.rb # DBを初期化するツール
│   └── db_upgrate.rb # model更新した時にDBスキーマを更新するツール
├── bootstrap.rb # これを読み込むとconfig.ymlを読んだりmodels/controllers/hellpersを一括読み込みしたりできる
├── config.ru # RackUp用の設定
├── config.yml # 設定ファイル
├── controllers # sinatraのルーティングメソッドを書いたファイルを入れるディレクトリ
│   ├── css.rb
│   └── main.rb
├── helpers # 便利関数を入れるディレクトリ
│   └── helper.rb
├── inits # DB接続とか、一番最初に実行すべき処理を書く
│   └── db.rb
├── models
│   └── omikuji.rb # DBのQueryとかを集約する
├── public
│   └── js
│   ├── jquery.js
│   └── main.js # index.hamlから読まれるJS
├── sample.config.yml # config.ymlにリネームして使う
└── views
├── index.haml # レイアウト抜きのメインコンテンツ部分
├── layout.haml # レイアウト
└── main.scss # cssのテンプレ


■DataMapperとMySQLを使う
MySQL版はこんなの(デモ)

MySQLのセットアップは以前書いた


この下に書いてある事はREADMEにも書いてある。


cloneしてくる
% git clone git://github.com/shokai/sinatra-template.git
% cd sinatra-template
% git branch -a
% git checkout -b dm-mysql remotes/origin/dm-mysql


設定
% cp sample.config.yml config.yml
config.ymlのMySQLのパスワード等を変更する


DB作る。最初のDB作る部分はログインして自分で作らないとならない。
% mysql -u your_name -p
mysql> create database sinatra_template
% ruby bin/db_migrate.rb
DB初期化するけどいいか?と訊かれるのでYesと答えよう。


起動
% gem install foreman bundler
% bundle install
% foreman start
http://localhost:8080 で起動する。


なんかすごい当たり前の話なんだけど、単純なWebアプリではなく、クローラとか色々と組み合わせたアプリを書くことが多い。
クローラとWebアプリが同じModelを読み込んで、同じ設定ファイルを読んで同じDBに接続するようにすると、綺麗に実装できる。

conosle.rbみたいな書き方をすると良い。

#!/usr/bin/env ruby
require 'rubygems'
require 'bundler/setup'
require File.dirname(__FILE__)+'/../bootstrap'
Bootstrap.init :inits, :models
これだけでDB接続してModelを読み込めるので、あとは普通に書けばいい。

設定ファイル(config.yml)の中身も、main.rbでやっているように
@title = Conf['title']
result = Conf['omikuji'].choice
みたいに取り出せる。