まだ重たいCMSをお使いですか?
毎秒1000リクエスト を捌く超高速CMS「adiary

2010/01/09(土)Perl/UTF8, 日本語「全角」→「半角」変換ルーチン

よくネットショップ等で買い物をすると

郵便番号は半角で入力してください。

とか言われてウザくないですか? こういうのを解消するためのルーチンです。

条件

  • Perl 5.8以降
  • 利用可能文字列はutf-8のみ(そうでない場合はutf8に変換して渡してください)
  • WTFPL(PDS扱いでも可)。

ソースは必ずutf-8で保存してください。

日本語に混ざる全角英数等を半角にする

use utf8;
use Encode ();
sub utf8_zen2han {
	my $str = shift;
	my $flag = utf8::is_utf8($str);
	Encode::_utf8_on($str);

	$str =~ tr/ !”#$%&’()*+,-./0-9:;<=>?@A-Z[¥]^_`a-z{|}/ -}/;

	if (!$flag) { Encode::_utf8_off($str); }
	return $str;
}

実行例。

(変換前)abcdefgさささ110-2244あいう##$”
(変換後)abcdefgさささ110-2244あいう##$"

半角カタカナを全角カタカナにする

use utf8;
use Encode ();

my %hankana_map = (
'ガ'=>'ガ','ギ'=>'ギ','グ'=>'グ','ゲ'=>'ゲ','ゴ'=>'ゴ',
'ザ'=>'ザ','ジ'=>'ジ','ズ'=>'ズ','ゼ'=>'ゼ','ゾ'=>'ゾ',
'ダ'=>'ダ','ヂ'=>'ヂ','ヅ'=>'ヅ','デ'=>'デ','ド'=>'ド',
'バ'=>'バ','ビ'=>'ビ','ブ'=>'ブ','ベ'=>'ベ','ボ'=>'ボ',
'パ'=>'パ','ピ'=>'ピ','プ'=>'プ','ペ'=>'ペ','ポ'=>'ポ',
'ヴ'=>'ヴ');

sub utf8_hankana2zen {
	my $str = shift;

	my $flag = utf8::is_utf8($$str);
	Encode::_utf8_on($$str);

	$str =~ s/(ガ|ギ|グ|ゲ|ゴ|ザ|ジ|ズ|ゼ|ゾ|ダ|ヂ|ヅ|デ|ド|バ|ビ|ブ|ベ|ボ|パ|ピ|プ|ペ|ポ|ヴ)/$hankana_map{$1}/g;
	$str =~ tr/。-゚/。「」、・ヲァィゥェォャュョッーアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン゛゜/;

	if (!$flag) { Encode::_utf8_off($str); }
	return $str;
}

Encode::JP::H2Z

今更使いませんが、EUC-JPの場合は標準モジュールでもできます。逆変換は Encode::JP::H2Z::z2h()。

use Encode ();
use Encode::JP::H2Z ();

sub eucjp_hankana2zen {
	my $str = shift;
	Encode::JP::H2Z::h2z(\$str);
	return $str;
}

その他

バグ等あったらコメントください。

2009/12/16(水)adiary Ver2.12リリース情報

Ver2.11に対しバグ修正を行いました。変更点を参照の上、必要ならばバージョンアップしてください。

ダウンロードはこちらから

Ver2.11→Ver2.12の変更点

  • テーマディレクトリを変更すると「印刷用の表示」がリンク切れになるバグを修正。
  • クラス追加。div.hatena-body0, a.adiary, li.to-album (_frame.html/_sidebar.html)
  • 浅野さんご指摘&パッチ(感謝)。
    • アルバムのサムネイルビューでサムネイル上にマウスカーソルを置いたときにポップアップする情報が実際のファイルと一致しないバグを修正。
    • アルバムのスライドショーでファイルを選択したときに左上に表示されるファイル名とバイト数が実際のファイルと一致しないバグを修正。
    • アルバムで「すべて選択」「選択を解除」ボタンをクリックしても正常に動作しないバグを修正。
    • 携帯画面でのコメント表示がエラーになることがある不具合を修正。
    • スーパーpre記法中等に行頭にスペースが1つ入る仕様を変更したことによる、バグ混入の問題を修正。*1
    • aa記法が効かないバグを修正。
    • テーマ選択時に下フレームで表示される画面のHTML(スケルトン)が正しくなかった問題を修正。
    • テーマによって、「日記の削除/コメント・トラックバックの編集画面」でコメント・トラックバック件数部分の表示がおかしくなる不具合を修正。
  • Ver2.06以降、アップローダーで中間拡張子チェックで"adiary-1.00.tgz"などが跳ねられてしまう問題を解決するため、数字など特定の文字を含む拡張子を許可する機能をつけました。*2
  • アップローダーで0byteのファイルをアップロードできるようにしました。*3

*1 : 正規表現の/eスイッチ(実行構文属性)を付け忘れた(汗)

*2 : 詳しくは uploader.conf.cgi の <$v.allow_ex_match=""> を参照してください。

*3 : 今までは明示的エラーにならずアップロードもできなかったため、何が起きてるのかよく分からないという状態。

Version2.00(β含む)以降からの乗り換え

  • そのまま上書きしてください。
  • Ver2.11以前の場合、uploader.conf.cgi をサンプルから再生成してください。

なおVer2.06よりアルバムシステム関連のJavaScriptの置き場が変更になっていますので、紛らわしい場合は theme/*.js を消してから上書きしてください。

アップデート方法の参考情報。

Version1.44以前(C73/2.00α含む)以前からの乗り換え

Version2.00への移行処理を先に行ってください。

2009/11/30(月)CORESERVER、メールの凶悪仕様(Gmailとの不具合の理由)

訳あってレンタルサーバのCORESERVER.JP(Value Domain)の利用を検討していたのですが、メールがらみでひどい素敵仕様を発見したので記録しておきます。GmailとCORE SERVERの相性が悪いという話がネットであちこち書かれていますが、おそらくこれが原因ではないかと思います。

メールの仕組みについて

問題について述べる前にメールサーバについて簡単におさらいしておきます。そんなの分かってるという人は飛ばしてください。

いわゆるメールサーバというのは、SMTPというプロトコルによってメールをサーバからサーバへ転送するサービスをしています。このSMTPというのは元々サーバも受信者も送信者もなにも区別がないプロトコルで、「SMTPサーバからSMTPサーバへどんどんタライまわしをしているといずれ受信サーバにたどりつく」というSPAMなんて存在しない、インターネットが限られた利用者が使用する紳士的な世界だった頃に作られました。その名残から、今もって

  • メール送信時にメールクライアントソフト(メーラー)が自分のメール送信用サーバに対してメールのデータを送るSMTP
  • メールサーバ間でメールのデータをやりとりするときに使われるSMTP

2つには何の差も存在しません。こんな仕様だから、メール設定のいわゆるSMTPサーバ(送信サーバ)として使用するサーバには何かしら制限を加えておかないと、世界中からそのサーバを中継(経由)してSPAMを送り放題になってしまいます。このように「本来想定している人以外からのメールを送ること」を第三者中継といいます*1。第三者中継をしないために通常は次のような制限を加えておきます(いずれかもしくは複数)。

  • 特定のIPからしか送信できないようにする。プロバイダのSMTPサーバが、そのプロバイダの利用者IPのみに中継を許可する。
  • SMTP-AUTH等の認証をする。ID/パスワードを確認するので、正規のID/パスワードを持つ利用者のみに中継を許可できる。
  • POP before SMTPという特殊な方法を利用する。メール送信前に1度メールを受信させることで、そのIP利用者に短時間だけ(1~5分程度)中継を許可する。

最初の方法だけで済めばよいのですが、どうしてもホテルやモバイルパソコンからメールを送信したいとなったときに必ずしも同じプロバイダを利用できるとは限らないので他の方法が必要になります。SMTP-AUTHがまだそんなに普及していなかった頃*2、過渡的な措置としてPOP before SMTPというものが考えられました。

以上はあくまで送る側の話で、SMTPサーバは送るためのサーバであると同時に受信サーバでもあることが多くあります*3。このままだと何処から送られてくるかわからない自分宛のメールを受け取れなくなってしまうので、自分宛のメールは無条件で受け取るという設定をします。例えば、xxx@foo.com ならば foo.com は自分宛ですよと登録しておきます。

*1 : 199x年頃はこの第三者中継が標準でonになっていたのだから恐ろしいものです。

*2 : いくらその仕組みを用意してもクライアントソフトが対応するまで利用できる人が限られていた

*3 : この2つは分けることもできるが、小規模なケースでは一緒にしてしまうことが多い

CORESERVERのおさらい

  • CORESERVERは送信メールサーバ=受信メールサーバ
  • CORESERVERはPOP before SMTPのみ対応(SMTP-AUTH未対応

CORESERVERの血迷っ…素敵過ぎる仕様

CORESERVERで普通にメールサーバを設定して、設定したサーバ宛にメールを送ろうとしたらこんなエラーメールが帰ってきました。

<test@s1xx.coresv.jp>: host s1xx.coresv.jp[202.172.28.1xx] said: 552 sorry, your
    domain isn't in my list of allowed senderhosts (#5.7.1) (in reply to MAIL
    FROM command)

要するに「あなたのドメインは送信元ドメイン許可リストに入っていません」と言っている。にも関わらず、他のぜんぜん関係ないメールサーバ(メールアカウント)からメールを送るとちゃんと受け取る。さっぱり意味不明。検索すると同じようにハマっている人多数

色々調べてみたところ、原因がはっきりしました。

  • POP before SMTPの設定時間(タイムアウト)が3時間以上!
  • POP before SMTPで「リレー許可リスト」に登録されると、そのIPからのSMTP接続はすべて外部への中継(リレー)として扱われる

どちらの仕様もバカ素敵すぎて言葉もない。

つまり、

  1. SMTPサーバでもあるIPで、CORESERVERからメールを受信する。
  2. CORESERVER側で「リレー許可リスト」に登録される。
  3. 手元のSMTPサーバからCORESERVER宛にメールを送信する。
  4. CORESERVERは「リレー許可リスト」に登録さているという理由だけで、外部から自分宛のメール送信を強制的に内部(CORESERVER)から外部へのメール送信と見なす。
  5. CORESERVERは外部へのメール送信のとき、予め登録された自ドメイン(senderhosts)にSMTPのFROM*4ドメインが含まれないためメールを拒否する。
  6. CORESERVERは自分宛のメールを552のエラーとして受取拒否する

ということです。

そしておそらく「リレー許可リスト」はサーバごとに全ユーザ共有だと思われるので(未確認)、たとえ自分でPOPしていなくても時々メールが送れないサーバが出てくる……と。

Gmailには

Gmailには、Gmailに統合してあるアカウント名義でメールを送信する目的で外部SMTPサーバを使用してメールを送信する機能がありまして、それを使うとバッチリメールが送れなくなるわけです。

これをしないと、GmailではFromの代理送信表示がされてしまいます。

*4 : SMTPにおけるFROMであるのでFROMヘッダとは異なる

総評

VALUE DOMAINのDNS設定も、何行か書くとすぐバグるし(行を入れ替えないとダメとか)、いまいちツメが甘い印象がぬぐえない。所詮、安かろうなんたらでしょうか。サーバスペックはいいんだけど、障害対応も遅いみたいだしねぇ。今回は安定性重視なのでCORESERVERは却下の方向。

どこか、もちょっとマトモなレンタルサーバ知りませんか?(苦笑)

追記

なんかググってたらこの記事のほぼコピペのブログが。せめてリンクぐらいしようや……。*5

*5 : 少し書き換えてあるのがなおたち悪い。どっちにしろ著作権的にはアウトだけども。

2009/11/23(月)adiary Ver2.11リリース情報

Ver2.09に対し、細かな機能変更とバグ修正を行いました。

ダウンロードはこちらから

Ver2.09→Ver2.11の変更点

  • 擬似データベース(pseudo DB)使用時、wikiコンテンツを開こうとするとDB速度が著しく低下する問題を修正しました。擬似データベース使用者は「管理」→「システム管理」→「管理者メニュー」→「Version 2.09以前 → Version 2.10以降へのアップグレード」を実行してください。擬似データベース以外は実行できません。
  • アルバムにソートの機能が付きました。
  • 一部テーマファイルのCSS修正。
  • 静的出力時に埋め込みテキストを出力しないオプションを追加。
  • MySQLにて記事テキスト本文に64KBまでしか保存できない問題を修正しました。(新規に作成した日記帳のみ有効)*1([ml:users:356:Thanks to ひとぅ])
  • rss10.html, rss10short.html, rss20short.html でRSSが生成できないバグを修正しました。([ml:users:361:Thanks to 小沼])
  • 次のような記述ができないコンパイラのバグを改善しました。
    <$ifexec(index(ENV.HTTP_USER_AGENT, 'iPhone')>=0, begin)>
    
  • adiaryスーパーpre記法に行頭にスペースが1つ入ることがある仕様を改善しました。(Thanks to nblog
  • theme?xxx/xxx といったページ表示時にそのページを検索対象外にするmetaタグを出力するようにしました。
  • 月別リストの古い年を折りたたむようにしました。(Thanks to G*2
  • C74で配布したbigblueを収録しました。
  • C76で配布したyuuテーマを収録しました。
  • 【携帯】コメントの表示件数を10件ごとにしました。

なお、[ml:users:0369:メーリングリストにある]のようなiPhone専用テーマを作成したいときは次のような細工をしてください。(関連URL*3

<$ifexec(Is_mobile, begin)>
	<$v.template_dir = "<@theme_dir>satsuki-mobile/">
	<$v.theme        = 'satsuki-mobile')>
<$end>

の後ろで

<$ifexec(0 <= index(ENV.HTTP_USER_AGENT, 'iPhone'), begin)>
      <$v.template_dir = "<@theme_dir>satsuki-mobile/">
      <$v.theme        = 'iphone'>
      <$Is_mobile=1>
<$end>

Ver2.11β1からの変更ファイル

  • diary.skel/format/month_list.html
  • lib/Satsuki/Diary.pm

*1 : 既存のテーブルに対してこの変更を行いたい場合は[ml:users:357:直接SQLを発行して修正してください]。

*2 : この変更を直ちに適用するには、必要な場合は日記帳の設定を保存するか適当な記事を開いて保存してください。

*3 : Ver2系はこの辺を吸収するにはちょっと無理がありますね。

Version2.00(β含む)以降からの乗り換え

  • そのまま上書きしてください。
  • Ver2.05以前の場合、uploader.conf.cgi をサンプルから再生成してください。

なおVer2.06よりアルバムシステム関連のJavaScriptの置き場が変更になっていますので、紛らわしい場合は theme/*.js を消してから上書きしてください。

アップデート方法の参考情報。

Version1.44以前(C73/2.00α含む)以前からの乗り換え

Version2.00への移行処理を先に行ってください。

2009/09/04(金)MySQL/PostgreSQLでのシリアル値まとめ

adiaryではすべてのテーブルに pkey というシリアル値(PRIMARY KEY)を設定しています。その扱いについて。

特にMySQLで特別な加工をせず、安全にシリアル値を取得する方法。

PostgreSQLの場合

PostgreSQLではそのままserial型というものがあり、

CREATE TABLE test(pkey SERIAL PRIMARY KEY, x INT);
INSERT INTO test(x) VALUES(10);

とすることで、pkeyをプライマリキーとして自動的に生成することができます。

PostgreSQLではシーケンス操作関数というものがあり、SERIAL型を定義すると自動的に作成されます。

例えば、現在の値を取得したければ

SELECT currval(pg_catalog.pg_get_serial_sequence('test', 'pkey'))

とします。pkeyに値を設定して INSERT したとき、シーケンス関数は自動的に再定義されないため次の関数で再設定する必要があります。

SELECT setval(pg_catalog.pg_get_serial_sequence('test', 'pkey'), (SELECT max(pkey) FROM test))

現在の値(最後に生成された値)を取得するには次のようにします。

SELECT currval(pg_catalog.pg_get_serial_sequence('test', 'pkey'), (SELECT max(pkey) FROM test))

同じセッション内でテーブル問わず最後に生成された値を取得する場合は次で済みます。

SELECT lastval()

安全に(他のセッションともかぶらない唯一無二な)次のシリアル値を取得するには、次のようにします。

SELECT nextval(pg_catalog.pg_get_serial_sequence('test', 'pkey'))

MySQLの場合

MySQLにも5.1以降(それより前から?)SERIAL型があります。

CREATE TABLE test(pkey SERIAL PRIMARY KEY, x INT);
INSERT INTO test(x) VALUES(10);

これによりpkeyに自動的にシリアル値を設定することができます。

  • SERIALは「BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE」のエイリアスです。
  • AUTO_INCREMENTは1つのテーブルに1カラムしか指定できない。
  • AUTO_INCREMENTを指定したカラムに、例えばpkeyを指定してINSERTしても、AUTO_INCREMENTがよきにはからってくれる(PostgreSQLのように値を再設定する必要なし)。

現在の値(最後に生成された値)を取得するには次のようにします。

SELECT LAST_INSERT_ID();

ただしC API等では mysql_insert_id()というものがあるため、こちらを使用するほうが効率が良いようです。例えばPerl DBIならば $sth->{mysql_insertid}。

追記。

SELECT * FROM test WHERE pkey IS NULL;

以前はこの方法でも取得が可能でしたが、いつの間にか使えなくなっているようなので注意しましょう。

値を取得、値の変更

SHOW TABLE STATUS WHERE NAME = 'test';

で得られたテーブル情報の中からAuto_incrementカラムの値を参照します。得られるのは次に挿入される値です*1

何かの都合で値を設定するときは次のようにします。

ALTER TABLE test AUTO_INCREMENT=1;

安全に次の値を取得

MySQLでもっとも厄介なのはこれです。auto_incrementには安全に次の値を得る方法が提供されていません。別にシーケンステーブルを作る方法などがありますが、面倒です。

INSERTでしか得られないならINSERTしてしまえばいいやという単純な方法です。

INSERT INTO test() VALUES();
DELETE FROM test WHERE pkey=LAST_INSERT_ID();
SELECT LAST_INSERT_ID();

NOT NULL制約等が付いているとINSERTが失敗するので、CREATE TABLE等であらかじめ制約を満たすDEFAULT値を設定しておく必要があります。MyISAMではトランザクションが使えないため、NOT NULLかつUNIQUE制約が付いていると、複数のプロセスが同時に"INSERT INTO test() VALUES();"を実行したとき一方が失敗します。

MySQLの場合はINSERTしてUPDATEした方がスマートなのはたしかなのですが、PostgreSQLではシーケンス型の次の値を安全に取得できるし、INSERTに成功してUPDATEに失敗した場合を考えるとややこしいので。

*1 : この値を使って次のデータをINSERTするのは危険です。