思いっきり個人的な見解です。無知であるが故の部分も大きいと思います。
「そんなことねーぞ、こういうのもあるぞ」
っていうのがあると、教えて欲しかったりしてます。
正直、国内でのRuby人気は気になるのです。Rubyというか、RubyonRailsですよね。
2年位前は「Rubyすげえ!Railsすげえ!」だったんです。その前はPHPばっかり書いてました。
でも、RoR使うようになってフルスタックのフレームワークの便利さに魅了され、RoR一本でいくつもりでした。
まぁPHPの前はC#(ASP.NET)ばっかり書いてた頃もあったのですが。ちなみにその前はaspでした。
そんななか、Googleが導入し、GAEでも使えるPythonにも興味がありました。もともといろんな言語を使えるようになりたいとも思っていたので、新規プロジェクトの際にちと手を出してみたんですね。Python+Djangoに。
当時、Apacheにpassengerを組み合わせてRoRを走らせていたハセテツには一定時間ごとにインスタンスが終了されてしまうのが「なんだかなー」でした。アクセス数の多いサイトであれば気にならないのでしょうが、少ないサイトのだったのでリクエスト時に多少のタイムラグがあることが気になってしまっていました。
Python+Django、mod_wsgiはそれを解決してくれました。まぁmongrelでもよかったのかもしれませんが、当時は開発が止まっていた時期だったと。今はthinとかもあるから問題ないのかもしれませんね。
CakePHPやsymfonyは選択肢に入りませんでした。ステップ数が多くてリクエストごとのレスポンスが遅すぎました。APCとか使えばそれなりらしいのですが、そもそもPHPは手軽に書けるのが魅力だと思っていたので、フレームワークのお作法に従うのは違うなーと。
実際、Pythonがよかったのではなく、Djangoがよかったのかもしれません。それくらいDjangoはよくできていました。RoRのfind_by_sqlに匹敵するものがないのは残念ですが。
Djangoだとurlディスパッチャーは自分で設定というか、urlとコントローラを紐付けてやらないといけません。Railsのようにcontrollerが自動的にurlと紐付かれたりはしません。が、それが自由自在で便利でした。
フレームワークを経由しないでApacheから直接レスポンスを返す静的コンテンツを置くディレクトリの指定も簡単です。フレームワークから「ここに置け」って指定されることもありません。
Railsはループ処理のときは一件ごとにsqlを発行しましたが、結合して一気に取ってきてくれます。ちゃんとO/Rマッパーが外部結合してくれます。
Djangoも、syncdbした後にmodelの設計を変えると面倒くさいという不満はあります。まぁそれは事前にきちんと設計しておけばいいだけの話なのですが。
モジュールの多さという点では、PythonもRubyもPHPも必要なものはそろっているという印象です。「RubyにはあるけどPythonにはない」っていうのはなかったかと。
あ、PHP版のMeCabバインディングはなかったか。
あ、あとRoRってひとつのプロジェクトで複数のWebアプリって作れませんよね。
実際こうやって書き出してみると、たいした理由じゃないですね。ちっさな「うーん、なんだかなー」の積み重ねだったりしてますね。
でも、そういう小さな積み重ねって技術者にとっては大事だったりすると思います。不満だった点も、当時はそうだっただけで今はとっくに改善されているのかもしれません。
結局、Python書けるようになってもGAEのアプリは一本も書いてません。それでも、Python+Djangoを選択してよかったと思っています。
うまい感じにオチをつけられないのですが、いろいろ試した結果、もうしばらくはPython+Djangoでいってみようと思います。
メインの言語を持った上で、いろいろな言語、環境のプロジェクトがこなせるように勉強だけは欠かさずにしないといけませんね。
技術者は死ぬまで追いかけ続けるんだろうな。下りのエスカレーターを上っていくように。
立ち止まったらどんどん落ちていくんでしょうね。
OAuthを使えるようになっておけば今後いろいろなマッシュアップに便利だろうと思い、試してみました。RoRで書いたのは、最近ご無沙汰過ぎてあまりに覚えてなかったのでリハビリを兼ねたわけです。
class TwitterAuth
require ‘oauth’
def self.consumer
OAuth::Consumer.new(
[Consumer key],
[Consumer secret],
:site => “http://api.twitter.com”
)
end
def self.request_token(_token, _secret)
OAuth::RequestToken.new(
consumer,
_token,
_secret
)
end
def self.access_token(_token, _secret)
OAuth::AccessToken.new(
consumer,
_token,
_secret
)
end
end
まずは上記のクラスを作成。OAuth関連の処理はこっちにやらせます。で、次がコントローラー。
require ‘rubytter’
class AuthController < ApplicationController
def index
request_token = TwitterAuth.consumer.get_request_token(:oauth_callback => “http://#{request.host_with_port}/auth/callback”)
session[:request_token] = request_token
redirect_to request_token.authorize_url
end
def callback
_token = session[:request_token]
request_token = TwitterAuth.request_token(_token.token, _token.secret)
access_token = request_token.get_access_token(
{},
:oauth_token => params[:oauth_token],
:oauth_verifier => params[:oauth_verifier]
)
session[:request_token]=nil
_account = {
:user_id => access_token.params[:user_id],
:token => access_token.token,
:secret => access_token.secret
}
session[:account] = _account
redirect_to :action => “timeline”
end
def timeline
_account = session[:account]
token = TwitterAuth.access_token(_account[:token] , _account[:secret])
_twitter = OAuthRubytter.new(token)
@user_timeline = _twitter.user_timeline(_account[:user_id],:count => 100)
end
end
def timelineが自分のタイムラインを取ってくるところで、「:count=>100」と指定しているのは「新着100件取ってきて」っていうことです。ここを指定しないでおくと規定値では20件取得してくれます。def callbackでセッションに入れたuser_id、token、secretをデータベース等に保存しておけば次回以降はこれらだけで認証とツイートの取得ができます(というか認証自体はtokenとsecretだけでOK)。
Twitterのアプリケーション登録申請で登録する際、コールバックURLを指定しないとWebアプリケーションとして登録されません。「開発中だからURLなんて決まってないよ」っていうケースでも、適当なURLを登録しておいてください。どうせ実際のコールバックURLはRubyのスクリプトで指定しなおしています。ハセテツはここに気が付かず、そこそこの時間を浪費しました。
で、タイムラインを表示するviewは
<ul>
<% for item in @user_timeline do -%>
<li><%= item.text %></li>
<% end -%>
</ul>
こんな感じです。
前回のエントリが結局フレームワークの話になってしまったため、新しいエントリを書きました。
同じような処理をさせた場合、どの言語が一番早いのか。非常に気になるところでありますが、試したことがありませんでした。今回試してみたところ、意外な結果が出たのです。
下の方に書いたそれぞれのプログラムを実行した結果の、プログラム内部で計測したミリ秒を比較してます。
PHP |
Ruby |
Python |
2.80秒 |
0.36秒 |
0.93秒 |
3回計測した平均です。同じ処理をさせるプログラムを書いたつもりですが、本当にフェアなものになっているのか正直自信がありません。そもそもPHPはWebアプリを主な目的としているとのことなので、こういう処理は得意としていないのかもしれない。
また、それぞれのプログラムのループ内2行目、文字列連結の部分をコメントアウトして実行してみると、これまた意外な結果が出ました。
PHP |
Ruby |
Python |
0.07秒 |
0.06秒 |
0.39秒 |
PHPは文字列連結が苦手なんだろうか。暗黙的な型変換にコストがかかるのだろうか、等々考えてしまいます。それとも秒を求める部分がネックなんですかね。その辺は今後追っていきます。
ただ、これまでPythonの方がRubyより早い、と思っていたために自分の無知さに猛烈に恥ずかしい思いをしています。
実際にMySQLと連携するバッチを書いたときにはRubyの方が遅かったり、Railsアプリがもっさりだったり、「速い」という印象がまったくありませんでした。が、それはある特定の面しかみていない上での思い込みだったのかもしれません。
これはRailsが遅いのか、もしくはMySQL/Rubyが遅いのか。当時の書き方がマズかったのか。今後、いろんなケースでこういった比較をしてみようかと。
今回の検証がすべてではないし、これが答えだとも思っていません。また、そういう意図を含んだエントリではありません。今回の検証だって、特定の面しか見ていません。が、あまりに意外な結果だったため、自戒の念を含めてブログに書きました。
「こう書くともっとフェアだよ」「こういうテストの方がいいんじゃない」等、アドバイスいただけるとうれしいです。
フレームワークまで含めた検証した方が現実的なのだろうか。。。
PHP
#!/usr/bin/php
<?
function microtime_float()
{
list($usec, $sec) = explode(” “, microtime());
return ((float)$usec + (float)$sec);
}
$_start = microtime_float();
$_sec = date(‘s’);
for($i=1;$i<100000;$i++)
{
$_val = (mt_rand(0, 100) + $i) / $i;
$_str = $_val.date(‘s’);
}
echo microtime_float() – $_start;
?>
Ruby
#! /usr/local/bin/ruby
i=1
_start = Time.now
99999.times do
_val = rand(100) / i
_str = _val.to_s + Time.now.sec.to_s
i += 1
end
p (Time.now – _start).to_f
Python
#!/usr/bin/python
# -*- coding: utf-8 -*-
import datetime
import random
_start = datetime.datetime.now()
for i in range(1, 100000):
_val = random.randint(0, 100) / i
_str = str(_val) + str(datetime.datetime.now().second)
print datetime.datetime.now() – _start
検証環境
CPU:Xeon 1.86GHz
メモリ:2GB
OS: CentOS5.4 32bit
PHP:5.2.11
Ruby:1.8.7
Python:2.5.2
ハセテツは複数の言語を状況に応じて使い分けてきましたが、最近のWeb系開発はPython(+Django)がメインになってきています。
ちょっと前までRuby(+Rails)だったのですが、いろいろと考えるところもあり、乗り換えました。その乗り換えた理由をまとめてみようと思います。
まとめてみたら「やっぱりRailsじゃね?」となるかもしれません。w
Ruby(+Rails)
- Rails便利すぎる。
- コントローラとモデルが別々のファイルになってくれるのはソースが追いやすい。
- Railsを通さない画像やCSSはpublicフォルダに置けばよいのはわかりやすい。
- urlディスパッチャーがいまいち使いにくい。(知らないだけかも)
- APサーバはmongrel一択?
- VirtualHost使おうとするとApache+Passenger(mod_rails)だが、これが重い。(チューニングで速くなるのかも)
- というか、Railsがそもそも重い。
- ちゅーか、Rubyが重い、遅い。
- PassengerはWindowsじゃ動かない。
- mongrelもPassengerより激速軽快かというと、そうでもない。
- gem便利すぎ。
- ワンライナで書く人が多くて、Perlとおなじ匂いがする。
ハセテツが使っていたRubyは1.8で、速くなったといわれる1.9には触れていないのでもしかすると古いのかもしれません。でも、Railsって1.9には対応してないですよね?(本日現在)
Python(+Django)
- Django便利すぎる。
- 日本語の書籍、情報が少なすぎる。(ハマるとキツい)
- modelが一枚のファイルなので、大量のmodelがあると可読性が激しくダウン。
- viewファイルをフォルダで分けたくても、アプリケーションフォルダ直下以外にviewファイル置くなら「sys.path.append」しないといけない。(違う?)
- urlディスパッチャーのカスタマイズが超便利。
- mod_pythonが使えるので、Windows環境だろうがLinux環境だろうが気にせずVirtualHostが使える。
- 速い。
- 日本語大変。Shift_JIS怖い。
- easy_installでパッケージのインストールは楽だが、管理が悪夢。
メリットとデメリットが入り乱れました。見難くてすいません。
結局フルスタックのフレームワークということで、DjangoとRailsに関しては一長一短だと思います。個人的にはRailsの方が良くできてたかなぁと。
最終的に、「RubyonRailsは重い」というオチになってしまうのです。Rubyも、Railsも、です。ただ、これはRuby1.9が実は劇的に速くなっていて、1.9に対応したRailsがリリースされたらすべて解決されるのかもしれません。
それでも、いまのところPythonの軽快さ、シンプルさに満足しています。あと、GAEでPythonが動くのもいいですよね。
「Googleが導入した言語」というのがハセテツ的に琴線に触れたのかもしれません。
ミーハーですいません。w
もう全部UTF8にしたいです。
require ‘kconv’
StrUTF8 = 文字列.toutf8
StrShiftJIS = 文字列.tosjis
StrEUC = 文字列.toeuc
見たとおり、上からUTF8に変換、Shift_JISに変換、EUCに変換、です。
RubyでHTTP経由でのXMLの受信と解析でGETする方法は書きましたが、POSTまでは書いていませんでした。今回はRubyでPOSTする方法です。GETで猛烈に長いクエリをつければPOSTできなくても同じことが実現できますが、まぁそこは気にせず。
require ‘net/http’
Net::HTTP.version_1_2
http = Net::HTTP.new(ホスト名, 80)
response = http.post( ‘/hoge.php’, ‘a=hoge&b=hogehoge’ )
p response.body
これだけ。ホスト名にはhttpは付けません。ポート番号は省略しても大丈夫です。Content-Type等は必要に応じて。newするときの第三引数です。「application/x-www-form-urlencoded」でいいのかな?ハセテツはつけてませんが、きちんとPOSTできてます。あー、相手がUTF8じゃない場合とかは文字コードの指定が必要かも。
responseの中身を見ればHTTPステータスコードとかも入っていると思います。その辺は割愛。
次はファイルのアップロードのやり方なんかも調べてみましょう。使うかどうかは微妙な気もしますが。
まぁ簡単にいうとRSSリーダーみたいなことをしたいときに使います。相手がRSSじゃなくてもOKです。Web上のコンテンツを読み込みたいときに使います。ただ、今回のサンプルだとgetしかできないので、制限はあります。
require ‘open-uri’
open(“http://www.tt-house.com/atom.xml”){|f|
data = f.read
xmldoc = REXML::Document.new data
xmldoc.elements.each(“feed/entry/title”){|element|
p element.text
}
}
文字コードのことは気にしてません。
これでハセテツラボRSSのタイトル一覧が取得できます。他のサービスとのマッシュアップ等にも使えますが、やっぱりデータのpostもできないと使い勝手は悪いですよね。
postをするサンプルは次回にしましょう。
RubyonRailsアプリケーションでサーバサイドでファイル圧縮、パスワードの設定をしてダウンロードさせる必要があったので調べてみました。
ZipRubyがあったりRubyZipがあったり、どっちがどうなんだか、相変わらずRubyは混乱します。今回は評判のよさそうなZipRubyを使いました。gemでインストールできて、バージョンは0.2.9でした。バージョン低いのがちと気になります。
gem install zipruby
これだけでインストールは終わりです。
require ‘rubygems’
require ‘zipruby’
Zip::Archive.open(‘hoge.zip’,Zip::CREATE) do |arc|
arc.add_file(‘test.pdf’)
end
Zip::Archive.encrypt(‘hoge.zip’, ‘password’)
コレだけです。簡単すぎる。
複数ファイルをひとまとめにしたい場合は
arc.add_file(‘test.pdf’)
を繰り返せばOK。ただ、フォルダごとごっそり圧縮、という方法は不明。フォルダ名を引数に指定したら怒られてしまいました。当然の結果。
最後の行がパスワードの設定です。ZIPファイル作るときにパスワードを設定するのではなく、既存のZIPファイルに対してパスワードを設定する、というイメージですね。Lhaplusではちゃんと解凍できました。
MySQL/Ruby と Ruby/MySQL の二つがあるようです。どっちがどっちか混乱しましたが、MySQL/Rubyがgemでとってこれて、Cで書かれたAPIのようです。Ruby/MySQL はRubyで書かれているようで、比較的低速らしいです。今回はMySQL/Rubyを利用しました。
gem install mysql
で一式インストールできます。PHPのPEARやRubyのgemは、一度つかったらやめられません。これは堕落なのでしょうか。
で、今回はWindows上にMySQL5.0をインストールしてつないでみたのですが、バリバリ文字化けました。DBはUTF8で作ってあって、ソースも以下のようにUTF8であることを明記していました。
require ‘rubygems’
require “mysql”
require ‘iconv’
$KCODE = ‘UTF-8’
db = Mysql::connect(“ホスト名”, “ユーザ名”, “パスワード”, “DB名称”)
rs = db.query ‘クエリ’
rs.each do |item|
p item[0]
end
なんでなんで?と調べていたら、my.ini(あー、iniってあたりがWindowsだなー、Linuxだとmy.cnfだったかな?)の[mysqld]に
default-character-set=utf8
skip-character-set-client-handshake
の追記が必要らしい。これはクライアントからの接続時に「文字コードはUTF8でっせ」とデフォルトで設定してあげるためのものらしいです。「set names utf8」と同じ意味ですね。これで文字化けずにWindows上でRubyとMySQLを利用することが出来るようになりました。
これまではC#とSQL Serverで統計とか集計のプログラム書いてたけど、Rubyの方がラクチンなんですよね。Railsばっかり注目されてますけど、Rubyは便利ですよ。
Datetimeオブジェクト.strftime(“%Y/%m/%d %H:%M”)
上記で、「2008/12/30 3:42」といった感じでyyyy/mm/dd HH:MM形式で出力される。まぁどの言語も大して変わりませんな。