preload
12月 27

先日 GitHub というのを初めて使ってみて、テキトーな小物をちょいちょい晒していくにも都合が良さそうだということがわかったので、今回、標記のプログラムも晒してみる事にしました。使用許諾条件は、基本的には the MIT License に基づきますが、「(迷惑メール送信に用いるなど)悪用・乱用を禁止する」という条件を付加したいと思います(っていうほど作り込んでないので、まあ、あんまし意味のない付加条件だと思いますが)。



MbSmtp – GitHub



MbSmtp は、Net::SMTP の拡張クラスで、日本の携帯向けにできるだけ効率よくメールを送る事を目的とした SMTP クライアントです。



MbSmtp は以下の機能を提供しています。

  • SMTP セッションあたりの送信通数が設定値以下になるよう、セッションを自動分割制御します
  • キャリアからの通信ブロックが発生した際、 設定時間待機した後、
    設定回数、失敗したメールのみ再送リトライします
  • メール送信処理中にエラーとなるアドレスが含まれていた場合、同一セッションで未送信のアドレスリストからエラーアドレスを除去します
  • セッション終了時、 送信リポートの構造体を返します
    • リクエストメッセージ数, 送信完了メッセージ数, エラーメールアドレスなど

各種設定値は lib/mb_smtp.rb 内で指定されていますが、必ずしも適正値ではありません。適宜設定を変更してご利用ください。



以下、簡単な使用例です。基本的には Net::SMTP と同じように使えますが、キャリアメールサーバは :docomo, :au, :softbank などといったシンボル指定で open できます。また、以下のようにセッション内である程度まとまった数を送信したとしても、1 セッションでの送信通数が設定値を超えないよう、勝手にセッション分割してくれます。その他、キャリアのメールサーバからの応答に応じて、エラーアドレスの振り分けや送信成功数のカウント、キャリアブロックに対する待機などをおこない、最後に MbSmtp::Result 形式でのレポートを生成する、といった動きをするようになっています。

@mail = TMail::Mail.new # MbMail::DMail とかでもOK
## @mail を適当に組み立てる
@result = MbSmtp.start(:docomo) do |smtp|
50.times do
smtp.send_mail @mail.encoded, @mail['from'].to_s, @mail['to'].to_s
end
end
puts @result # => MbSmtp::Result による送信結果レポート

この他の使い方や、動きの詳細については spec/mb_smtp/*.rb にある spec ファイルや lib/mb_smtp.rb 本体をご覧ください。



ちなみに MbSmtp は、元々は、個人的にメール配信時のキャリアのメールサーバの振る舞いを調べてみようとごにょごにょやっていたときの副産物です。実サービスでの利用実績はありませんし、これにより高速に携帯メール配信ができるという保証も検証結果も一切ありません。もちろん、これを使用した事で、キャリアブロックされまくってマトモにメールが送れなくなった、とかになっても、僕の方で責任はとれませんです。あくまでご参考まで、ということで。。

Tagged with:
12月 25

git とか GitHub とかいうのを使った事がなかったので、このたび初めて使ってみました。というわけで、標記の Rails プラグインを the MIT License にて公開します。


MbMail – GitHub


MbMail は、Rails で日本の携帯向けサービスをつくる際、 メール扱い周りで発生するちょっとした面倒を回避するための小物寄せ集めプラグインです。rails-2.0.2, tmail-1.1.0 環境下での動作を確認しています。また、rails プラグイン形式で配布していますが、MbMail::DMail は rails 環境に関係なく使えます。


MbMail は以下の機能を提供しています。

  • MbMail::DMail
    • キャリア間HTMLメール(デコメール, デコレーションメール, デコレメール)の相互変換
  • MbMail::MbMailer
    • ActionMailer の拡張
      • 標準で使用する charset (エンコーディング)を ISO-2022-JP に変更
      • 文字コード変換器に NKF を使用するように変更 (機種依存文字の送信を可能に)
      • 日本語ヘッダを作成するためのメソッド base64 を追加 (Mailer 内で日本語Subjectや日本語Fromを簡単に記述)
      • RFC違反のドットが連続するアドレスが扱えるよう、TMail::Parser を置き換え

たとえば、以下のように au デコレーションメール形式のファイルがある場合、これを load し to_docomo_format とすれば、docomo のデコメールフォーマットが得られます。

au = MbMail::DMail.load("au_decoration_mail.eml")
docomo = au.to_docomo_format
puts docomo.encoded # => docomo デコメールフォーマット文字列

その他、使用方法サンプルは、git リポジトリの中の spec ファイルたちをご参考ください。


パッケージに含まれるクラスのうち、MbMail::MbMailer は以前、こちらのエントリでも取り上げた「ドット連続アドレス問題」などに対応するものです。ただし、MbMailer を使用する事により TMail::Parser を書き換えてしまいますので、ご注意ください(MbMailer を使用するに当たって、ActionMailer の使い勝手を残しつつ TMail の一部だけ書き換える、ウマい方法が思いつかず。。誰か助けて)。


MbMail::DMail も MbMail::MbMailer も個人的な利用実績がありますので、基本的な部分はそこそこ使えると思います。が、プラグイン形式に切り出したのはこれが初めてですので、何かと不都合があるかもしれません。(絵文字の変換辺りで微妙にオカシイところがあるかも。)あと、git の使い方がよく分かってないうちは、何かと不慣れなことをしでかすかもしれません。諸々ご理解の上、ご利用いただければ幸いです。


なお、再掲を含みますが、上記のプラグイン提供に当たりお世話になった方々をご紹介しておきます。勉強させていただきました。ありがとうございました。

  • 携帯メールを扱うに当たっての base64 メソッドの実装や NKF 利用のアイデアとして、以下のサイト様を参考にさせていただきました
  • DMail の絵文字相互変換に Jpmobile の絵文字変換テーブルを使用させていただいています
  • ActionMailer の拡張時に TMail の幾つかのクラスを書き換え利用させていただいています
  • サンプルで使用している GIF 画像に 素材屋イチゴアポロ の素材を利用させていただいています

ついでに、以前こちらで紹介した「ケータイサイトで機種情報を取得する Rails プラグイン mbterm_db」も GitHub に引っ越しました。ついでに更新しようと思ったけど、更新の仕方を忘れたので、そのうちメンテします。


MbtermDb – GitHub


それでは皆様、よいクリスマス&よいお年をお迎えくださいませ。

Tagged with:
12月 11

以下のように around_filter 内で reset_session するアクションに対し、大量のアクセスを重ねていくと、メモリ使用量ががつがつ増大していく。

class FugaController < ApplicationController
around_filter :foo
def index
end
protected
def foo
reset_session
yield
end
end

こいつを script/server -e production で起動し、ab -n3000 http://localhost:3000/fuga/ などにより多量のリクエストを送ると、メモリ使用量が 80MB を超えるまでに増大し、リクエスト完了後もまったく解放されない。foo 内で yield しなくても同様。セッションストレージにデフォルトの CookieStore を使った場合のほか、:active_record_store, :mem_cache_store を使っても同じ。session_options[:expires] の設定も関係無し。
なお、before_filter や after_filter で reset_session した場合は、問題は起こらないようだ。また、rails-2.0.2 環境で発見したが、rails-2.2.2 でも再現する。

状況解析のため、こちらのエントリ(Memory leak profiling with Rails) を参考にメモリ利用状況をプロファイルしてみたところ、around_filter を使った場合は String のインスタンス数が一気に膨れ上がった後、一向に解放されないことがわかった。(before_filter を使った場合は、リクエスト処理完了後、それなりに解放された。)

次に、この String の正体を見るために、上記の MemoryProfiler の :string_debug オプションを使って ObjectSpace 中の String をダンプしてみたところ、session_id などセッションデータと思しき文字列が大量に入っていた。リクエスト終了後は、CgiRequest や CGI::Session などのリクエストやセッションに絡むオブジェクトはすぐに解放されていた事から、何らかの原因でセッション内文字列のみ GC 対象にならない状態になっていると思われる。

さらに、上記の String がどの時点で生成されているのかを追ってみた。

actionpack-2.0.2/lib/action_controller/cgi_process.rb L.150-

private
# Delete an old session if it exists then create a new one.
def new_session
if @session_options == false
Hash.new
else
CGI::Session.new(@cgi, session_options_with_string_keys.merge("new_session" => false)).delete rescue nil
CGI::Session.new(@cgi, session_options_with_string_keys.merge("new_session" => true))
end
end

どうも上記の CgiRequest#new_session の CGI::Session.new で確保されているようだ。何が原因なのか追いたいが、CGI::Session は ActionPack で拡張されており、どうも切り分けが難しい。いろいろ書き換えながら試してみたところ、オリジナルの CGI::Session 中:

lib/ruby/1.8/cgi/session.rb L.299

ObjectSpace::define_finalizer(self, Session::callback(@dbprot))

ここのファイナライザ定義があることで、around_filter でのみリークするようだ(これを試しに無効にすると、リークしない。謎)。かといって、こんなところを無効にするわけにはいかず.. と、今度は around_filter と before_filter とで、何が変わってきているのか、を追ってみた。

actionpack-2.0.2/lib/action_controller/filters.rb L.709-

def run_before_filters(chain, index, nesting)
while chain[index]
filter, index = skip_excluded_filters(chain, index)
break unless filter # end of call chain reached
case filter.type
when :before
filter.run(self)  # invoke before filter
index = index.next
break if @before_filter_chain_aborted
when :around
yielded = false
filter.call(self) do
yielded = true
# all remaining before and around filters will be run in this call
index = call_filters(chain, index.next, nesting.next)
end
halt_filter_chain(filter, :did_not_yield) unless yielded
break
else
break  # no before or around filters left
end
end
index
end

ソースを追う限り、filter 処理は適用順序に並べた配列形式に変換された後、before_filter も around_filter も上記の ActionController::Filters::InstanceMethods#run_before_filters で実際のフィルタ処理がおこなわれているように見える。で、around_filter では、上記の filter.call(self) へ渡しているブロックで index = call_filters している箇所があるが、いろいろ切り分けていってみるに、どうもここが怪しい。around_filter に入れ子になっている filter を再帰的に処理しているようだが、ここの中で CGI::Session.new するとセッション中の String のみが解放されない状態になってしまう.. ということだろうか。

もうひといきな気がするんだが、ちょっとツラくなってきたので、とりあえずここまでで POST しておく。Ruby の GC の問題なのか、Rails の黒魔術のせいなのか、まだ微妙なところだ。とりあえず before_filter にしとけば問題は起こらないのだが、多数のフィルタ処理をサイトの全域で使うようなサービスの場合、個々のコントローラには around_filter をひとつ書いておいて、その filter 内に多数のフィルタメソッドを並べる、というのがラクチンだとおもう。うーむ、悔しいが before_filter で回避してしまおうか.. フィルタ内で、セッション検査をおこなった上で、不正セッションであれば reset_session して仕切り直す、みたいなのって、わりとあるような気がするんだけどなあ.. ハマってる人が見当たらない。

Continue reading »

Tagged with:
2月 27

Amazon Web Service の Simple DB を試してみたけど、なかなかリクエストの作成がうまくいかなくて、一回リクエストに成功したところで、明日も早いしもう寝るか、というポスト。へにょいリクエスト作成用の Ruby クラス付き。


Amazon Simple DB (以下、ASDB)
は Web ベースで利用できるデータベースサービス。REST やら SOAP やらでアクセスできる。利用には Amazon Web Service (以下、AWS) のアカウントと Simple DB サービスへのサインアップが必要。2007 年末くらいから公開されていたのだが、サインアップが遅れた (Limited Beta の) ため、順番待ちに入っていた。で、1 月末にようやく使えるようになった。
ちなみに、S3 やら EC2 と同じく従量課金。手軽にスケールアウトできそうな反面、規模によっちゃ実はそんなに安くないんじゃないの?とハマりそうではある。(EC2 立ち上げっぱなしで放置してたら $70/月 くらいかかった。)

ASDB では、Domain というデータセットのハコに Item という単位でレコードを追加していく。Item には Attribute という名前で属性を付加する事ができ、記録されているレコードには Query によりアクセスする事ができる。ちょっと強引に RDB のように解釈するなら、Domain がテーブル、Item がレコード、Attribute はカラムみたいなものだとおもう。

まず、REST の API を使って、上記でいう Domain を定義する CreateDomain アクションを試してみる。実際の利用には、AWS にサインアップして得られる AWS Access Key ID と AWS Secret Access Key が必要。

ASDB へのリクエストの構成要素は大まかに以下のような感じ。これを GET のパラメタに含める。

  • AWS Access Key ID
  • アクション
  • アクション毎に指定するオプションパラメタ
  • タイムスタンプ
  • APIバージョン
  • シグネチャバージョン
  • シグネチャ

ここで、最後のシグネチャは、以下のようにして作る。

  1. シグネチャ以外の構成要素 (AWS Access Key ID 〜 API バージョン) をキー名で昇順ソートする
  2. ソートしたペアのキーと値をセパレータとか付けずに単純に並べる
  3. 並べたパラメタを HMAC-SHA-1 にかけてメッセージダイジェストを得る。この時の秘密鍵に AWS Secret Access Key を指定する
  4. 得られたダイジェストを Base64 エンコードする
  5. 得られたエンコード文字列を更に URL エンコードする

このようにして得られたシグネチャを Signature 値としてリクエストに含める事で、ASDB がメッセージの正当性を確認してくれるというしくみ。

で、このリクエストを作成するクラスが以下のもの (AWSACCESSKEY やら AWSSECRETKEY はサインアップして得られるものを使う):

require 'openssl'
require 'base64'
require 'cgi'
class AmazonSimpleDb
BASEURI = 'https://sdb.amazonaws.com/?'
AWSACCESSKEY = 'Your AWS Access Key'
AWSSECRETKEY = 'Your AWS Secret Key'
def initialize
@params = Hash.new
@params.store('AWSAccessKeyId', AWSACCESSKEY)
@params.store('SignatureVersion', '1')
@params.store('Version', '2007-11-07')
end
def add_param(key, value)
@params.store(key, value)
end
def request_uri
uri = BASEURI
message = ''
(@params.sort_by { |x| x[0].downcase }).each do |e|
uri << "#{CGI.escape(e[0])}=#{CGI.escape(e[1])}&"
message << "#{e[0]}#{e[1]}"
end
hmac = OpenSSL::HMAC.new(AWSSECRETKEY, OpenSSL::Digest::SHA1.new)
hmac.update(message)
signature = CGI.escape(Base64.encode64(hmac.digest).chop)
uri << "Signature=#{signature}"
end
end

sort_by してるんで、Ruby は 1.8 以上で。パラメタは case-sensitive なのに、ソート時は case-insensitive だったり、生メッセージは URL エンコード不要だったり(そりゃそうか)、Base64 したら最後に改行文字が入ってたりにハマった。

これを AmazonSimpleDb.rb とかで保存して、以下のような感じで使う。タイムスタンプで指定してる日付は適当。DomainName に DoCoMo とか指定してるのは、機種情報 DB を作ってみようとしただけで、深い意味は無し。XXX やら YYY はダミーです。

$ irb -r 'AmazonSimpleDb'
irb(main):001:0> asdb = AmazonSimpleDb.new
=> #"2007-11-07", "AWSAccessKeyId"=>"XXXXXXXXXXXXXXXX", "SignatureVersion"=>"1"}>
irb(main):002:0> asdb.add_param('Action', 'CreateDomain')
=> "CreateDomain"
irb(main):003:0> asdb.add_param('DomainName', 'DoCoMo')
=> "DoCoMo"
irb(main):004:0> asdb.add_param('Timestamp', '2008-02-26T23:27:00-09:00')
=> "2008-02-26T23:27:00-09:00"
irb(main):005:0> asdb.request_uri
=> "https://sdb.amazonaws.com/?Action=CreateDomain&
AWSAccessKeyId=XXXXXXXXXXXXXXXX&
DomainName=DoCoMo&
SignatureVersion=1&
Timestamp=2008-02-26T23%3A27%3A00-09%3A00&
Version=2007-11-07&
Signature=YYYYYYYYYYYY" (実際は一行)

得られた request_uri にブラウザとか適当な https が行けるクライアントからリクエストを投げれば XML で結果が返ってくる。net/http でアクセスするところはまだ作ってない。ていうか、まだ PutAttributes も Query も試してないし。これでバッコンバッコンリクエスト投げたらいくら請求くるんだろう。。誰かやってみたひと居ないかな..

参考:

Tagged with:
2月 25

実は前から公開してたんだけど、最近の機種情報を追加ついで、あと Rails のプラグインを書く勘を取り戻しついでに、ちょっと手を入れたので、改めて紹介しときます。

mbterm_db は Ruby on Rails でケータイサイトをつくる際に、ブラウザのバージョンや画面サイズを取得する Rails プラグインです。

このプラグインは、以前モバイル勉強会で紹介した機種情報 DB の出力の一形式です。機種情報 DB には、基本的に僕が必要なものしか入れてないのですが、最近は FlashLite に傾倒気味なので、そのうち FlashLite のバージョンくらいは入れるかもしれません。

こんなデータベースを持たずとも、ドコモ以外は HTTP リクエストヘッダにそこそこの情報が含まれているのですが、リクエストヘッダの情報は微妙に足りなかったり(一行の文字数とか)、SSL したら取れない事があったりと(SoftBankでたまにある)、結構ハマりがちです。まあ、あったらあったで越したことはないかな、ということで、ご入用の方、どうぞお持ちください。

インストール方法は以下の通り。詳しくは trac の方をご覧ください。jpmobile とか Mobile_on_Rails などのプラグインと併用するのも便利だと思います(使わせていただいてます。感謝! ;-)

$ script/plugin install http://jpmbdb.googlecode.com/svn/trunk/mbterm_db

Continue reading »

Tagged with: