良く使うんだがすぐに忘れるのでメモ。

use CGI;
my $F = CGI->new();
my $dir = $F->url;
$dir =~ s/[\/][^\/]*$//g;


たま~にforkして子プロセスを起動する処理を呼んだとき、親プロセスで上記の「Lost connection to MySQL server during query」エラーや、httpヘッダエラーが発生していた。

特に決済まわりで使っているわけでもないし、トランザクションが絶対必要なわけでもないし、子プロセス内の処理はとりあえず最後まで完了してるし、いままで「ま、いっか」って感じで放置していたが、最近頻発するようになってきたので、ようやく真面目に取り組んでみる。

DBIに接続したときのステートメントハンドルの扱い方が悪いんじゃないかっていいうのはうすうす気づいてたが、下記のような資料を発見。ありがたや。
http://d.hatena.ne.jp/hiratara/20110122/1295711939
http://nihen.hatenablog.com/entry/2011/11/17/102557


これの真似して、子プロセス生成直後に
$dbh->{InactiveDestroy} = 1;
$dbh = $dbh->clone({InactiveDestroy => 0});
を呼んだらなんとあっさり解決。

以前にこんなこと↓
http://www.igreks.jp/dev/2010/06/forkmysql.html
書いてたけど、やっぱ親プロセスと同じコネクションつかっちゃまずいわけね。
Apacheのせいにしてごめんなさいって感じですね。


久々の更新。

最近、WordPressのindexを自分のドメインのトップページに設定している人が増えてきている模様?

そういった場合、管理者は、WordPressの構成ファイルをドキュメントルート直下に展開するか、ドキュメントルート直下の.htaccess処理で、WordPressの本体ディレクトリ内のindex.phpにリダイレクトさせる。
もしくは、apacheの設定自体を変えて、ワードプレスの本体ディレクトリ自体をドキュメントルートにしちゃう、
主流はこの3つくらいだろうか。


先日MilkyStepの初期設定代行を行おうと思い、VPSのコンパネにアクセスしようと思ったら、いきなりワードプレスのスキンで404エラーが発生。

ドキュメントルート直下の.htaccessをのぞいてみると、どうやらお客さんの方で追記したと思われる、下記のようなRewriteRuleを発見。

# BEGIN WordPress

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>

# END WordPress


ふうむ。
どうやら最近はこういうのが流行っているのだろうか?

上記の設定だと、下位のファイルを指定せずトップページにアクセスすると、同列に置いてある「index,php」の制御により、ワードプレスのトップが表示される。
下位(ワードプレスディレクトリ内)の実在するファイルにアクセスすれば見れるが、実在しなければ「ありませんよ!」というメッセージ(多分ワードプレスの制御)が出る模様。

基本的に、RewriteRuleに書かれた以外のコンテンツ(またはそのエイリアス)が存在しない場合、
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
により、その下の
RewriteRule . /index.php [L]
が実行される。

ただし、ここで、
RewriteRule . /index.php [L]
となっている。
左辺を見ると、「.」となっているので、「.」(任意の一文字)が合致する名前のコンテンツは、基本的に「すべて」ということになる。

したがって、
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
は、基本的にははすべて「偽」となるため、
RewriteRule . /index.php [L]
の処理が実行されないことになる。
(それと、右辺は「/index.php」ではなく「index.php」ではなかろうか?)

とまあ、こう考えれば、WordPressディレクトリの外のコンテンツにアクセスするにも問題なさそうなのだが、なぜか(ワードプレス内のコンテンツが無いという)404となる。

多分、それ以前の大元のapacheのルールで、インデックスの指定のルールとかがごっちゃになって、こういう結果的にこうなっている思われる。

根本的な原因を探すのも面倒だし、もともと書いてあったRewriteRuleを消したりなんかすると、さらに被害が増えそうだ。

したがって、なるべく既存のコンテンツにあたりさわりなく行う対策としては、アクセスしたい同階層のディレクトリ名が指定された場合は、上記のRewriteRuleが適用されないようにするのが得策と考えた。

# BEGIN WordPress

RewriteEngine On
RewriteBase /

# Add -------
RewriteRule ^ControlPanel/* - [L]
RewriteRule ^mysqladmin/* - [L]
RewriteRule ^ms/* - [L]
# ------- Add

RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]


# END WordPress

これで、ControlPanel、mysqladmin、ms という名前のディレクトリには、
http://domain.com/ControlPanel/......
http://domain.com/mysqladmin/......
http://domain.com/ms/......
というURLでそのコンテンツにアクセスできる。

以上。
事業所の移転に伴って、フレッツ光の契約タイプを変更しなくてはいけなかったため、必然的に貸与されるルータも、PR200NEからPR400NE(一応最新版らしい)に変更することになった。

ついでにプロバイダもbiglobeをやめてインターリンクの固定IP付のもっと安いサービスに乗り換えた。

しかし、ルータが変わってから、LAN内ホストの名前解決(正引き)ができなくなってしまった。

うちの場合はLAN内のPCからLAN内のサーバにアクセスするとき、内部DNSを構築しているので、ルータの静的NATとサーバ側のファイアウォールを許可すれば、LAN内からでも「ホスト名+ドメイン名」でアクセスできるはず。

というか、ルータが変わる前は普通にできていた。

もちろん、「http://192.168.*.*/:80」とかやれば、当たり前だが普通にWEBサーバにつないでくれる。


ルータのソフトのファームウェアバージョンも前と変わってないし、設定も前とすべて同じにしてあるのに、なぜかLAN内のホストへ正引きできない。

仕方が無いので、現在はルータの設定で、DNSの問合せ先をすべて192.168.1.4(LAN内のDNSサーバ)に指定している。
こうするとちゃんと正引きしてくれるが、WAN側のホストにアクセスするときも、いちいち内部のDNSサーバに問合せに行くので、名前解決の時間が3秒くらいかかってイライラする。

確か、前は、ルータのDNS問合せ先を「自動的に取得する」にしておけば、WAN側・LAN側を勝手に判別してWAN側だったらプロバイダのDNSに問合せに行ってくれたと思ったのだが・・・

ルータ設定画面のメニューで、今まで使ったことがなかった「ローカルドメイン設定」というメニューがあるが、これにローカルドメインを登録しようとしても、バグってるぽくて有効にならない。

他に移転に伴って変わったことと言えば、プロバイダが変わったことだが、この現象はインターリンクに切り替える前からおこっていた現象なので、やはり怪しいのはルータということになる。


以下、私がNTT東日本に問い合わせたメールの内容。

この後、一応連絡が来て、結局他の部署のなんちゃらというところに問い合わせてみてください、といった無責任な対応だったので、めんどくさくてそのままにしている。

もし、同じような現象にある人は、下記の文面をそのままコピペして問合せ用に使っちゃってください。

もしくは、「お前の考え方が間違っている。ルータのせいではない。」という見識をお持ちの方は、ぜひぜひ解決策を教えていただけると嬉しいです。

----------------------------------------------------------------------------------------------
先日引越しを行った際に、弊社からの案内でルータをPR-200NEからPR-400NE(無線LANカード付き)に変更しました。

当方ではLAN内にサーバを構築しており、インターリンクというISPの固定IPアドレスによりWEBも公開しておりますが、LAN内のクライアントPCからLAN内のサーバにホスト名でアクセスする際に支障が出ております。

この場合、何も対策していない場合は、通常はLAN内のホストにホスト名+ドメインでアクセスすることはできませんが、その回避策として内部DNSを設置しており、引っ越し前(PR-200NE)までは正常に問合せ、閲覧ができていました。

ルータのソフトウェア(最新ファームウェアバージョンは1.11)の設定も、基本的に引っ越し前とすべて同じにしています。
静的NATの設定なども全て同じで、外部から当サーバへの各種アクセスは問題ありません。

「詳細設定」→「DNS設定」の「LAN側DNSサーバアドレス」に、当方の内部DNSアドレス(192.168.1.4)を指定しており、この設定が、LAN内ホストの名前解決をする際に使用されるDNSアドレスと認識しております。
この認識が間違っていれば申し訳ありません。

この認識から行くと、通常使用する接続設定(「基本設定」→「接続先設定→「現在利用中の接続名」」)において、「DNSサーバアドレス」で「サーバから割り当てられたアドレス」を「使用する」にしておいても、LAN内ホストにホスト名で接続するときは、上述した内部DNS(192.168.1.4)に問合せに行かなくてはならないはずなのに、そうならずに、「このページは表示できません」となります。
逆に、上記の「サーバから割り当てられたアドレス」を使用せず、問合せるDNSを強制的に内部のDNS(192.168.1.4)に指定すれば、当たり前ですがアクセスできます。
つまり、内部DNSの正引きは正しく動作していることになり、ルータのDNS問合せ先の判別がうまくいっていないのではないか、ということです。

以上よろしくお願いたします。

----------------------------------------------------------------------------------------------


今朝起きると、やけに仕事部屋が静かだ。

ふと見るとサーバがシーーーンとしている。というか電源が落ちている。


いままで定期的にメンテナンスをしてきたとはお世辞にも言えないので、いつかはこういう日が来るとは思っていたが、さすがにちょっとあせる。

とりあえず、再度立ちあげてみる。
ブートはOK。ログイン画面もOK。ログインも可能。

しかし3分後・・・
「ピュシューーーーン!」という音とともにサーバ停止。

今度はもう一度電源入れて、topで監視してみる。
特に問題なさそうな動きなのに、また「ピュシューーーーン!」と停止。

リソースが落ち着いているのに止まるってことは、一部のデーモンが暴走したり、スクリプトが暴走したりという原因ではなさそうだ。
それに全然熱も持ってない。素手で躯体を触ってもまったく熱くない。
サーマルシャットダウンではないのか???
そして、CPUがほとんど使われてないのに、ファンが常に全開で回りっぱなしだ。うるせー。

HDDだったらいやだな・・・いつバックアップ取ってたっけ?やっぱRaid組んどきゃよかったか・・・なんて思いながら仕方なくサーバを開けて中を確認。

一番怪しそうなCPUクーラーを外してみると、グリスのカスがバラバラ落ちてきた・・・あらら・・・
そして残ったグリスも完全に乾いててパキパキ状態。
そういえばこのサーバのクーラー外すの初めてだったかも・・・

しかもクーラーの足の4本のうち2本の樹脂部分が割れてる状態だし回転も悪い。
(ぐらついてはいないが)

ダメだこりゃ。交換だなと思って、クーラーだけを外して近所のパーツ屋に直行。

「急に電源が落ちるからCPUクーラーを取り換えたい」といったら、店員はきっと電源ユニットの故障だと言い張る。
しかし私は「いやいや、クーラーとCPUの密接状態が悪いから熱伝導が弱っているんだよ多分」
とかなんとか口論になりつつ、なかば強引に新品のクーラー(Core2Duoのサポートタイプ)を購入。

今回購入したのその店で一番安かったこれ。
⇒ Xdream p775

うちのサーバはDELL製でマザボと接続するコネクタのピンが4ピン(この製品は3ピン)なんだが、多分片側3ピンしか使ってないから挿さればいけるだろうってことで・・・

帰ってきて商品を開けた時に、そういえばシリコングリスも買わなきゃならなかったんじゃ?なんて心配になるが、ヒートシンク部分にグリスがあらかじめ塗ってあったので一安心。

この製品には、クーラーの足の下に置く架台みたいなパーツが付属してるが、うちのサーバはコンデンサが邪魔で付けられなかったため、架台なしで直接ねじで取り付けた。
(取り付けの際、グリスが服についてしまったのは言うまでもない)


そして改めて、電源を立ち上げる。

30分たっても落ちない。
どうやら成功のようだ。よかったよかった。

障害に気付いてから復旧まで約3時間。
情報科出身でも機械科出身でもない私にしてはまずまずでしょう。


参考資料:
http://pasokoma.jp/bbs8/lg245187
http://www.dosv.jp/feature/0606/14.htm
例えば以下のようなテーブルがあったとき。

user_tbl(会員テーブル)
id name address
1 山田 東京都
3 鈴木 千葉県
6 高橋 北海道
12 佐藤 大阪府

shitumon_tbl(質問テーブル)
id title answer_type
1 あなたの身長 テキスト入力
2 あなたの体重 テキスト入力
3 住居形態 択一選択

sentakushi_tbl(質問の選択肢テーブル)
id shitsumon_id sentakushi_name
1 3 一戸建て
2 3 マンション
3 3 アパート
4 3 ダンボール

user_info_tbl(会員情報テーブル)
id user_id shitsumon_id answer
(テキスト入力の時はその値、
択一選択の時は選択肢ID)
9 1(山田) 1(身長) 175(cm)
10 1(山田) 2(体重) 68(kg)
11 1(山田) 3(住居形態) 3(アパート)
12 3(鈴木) 1(身長) 168(cm)
13 3(鈴木) 3(住居形態) 1(一戸建て)
14 6(高橋) 2(体重) 74(kg)
15 6(高橋) 3(住居形態) 2(マンション)
16 12(佐藤) 1(身長) 182(cm)
17 12(佐藤) 3(住居形態) 3(アパート)


上記のとき、
※「身長」が「170」cm以上で、 ※「住居形態」が「アパート」の会員(条件数=2)の「会員テーブル」だけを表示する。

SQLの例
-----------------------------------------------------------------------------------------
SELECT `user_tbl` . *
FROM `user_tbl`

LEFT JOIN INNER JOIN (
# 副問い合わせ

# まず質問IDが1(身長)で、答えが170(cm)以上のuser_idを抽出
(
SELECT `user_info_tbl`.user_id
FROM `user_info_tbl`
WHERE
`user_info_tbl`.question_id = 1
AND
`user_info_tbl`.answer >= 170
)

# 次に質問IDが3(住居形態)で、答えが3(アパート)のレコードをUNION ALL で結合
# ※「UNION ALL」にすることで、わざと重複させる。
UNION ALL (
SELECT `user_info_tbl`.user_id
FROM `user_info_tbl`
WHERE
`user_info_tbl`.question_id = 3
AND
`user_info_tbl`.answer = 3
)

) AS uni
# ↑副問い合わせでUNIONするときは固有名をつけないと、「ERROR 1248 (42000): Every derived table must have its own alias」になる。

# ユーザIDが同じものを外部内部結合
ON user_tbl.id = `uni`.user_id

# 重複数が2(=条件数)のレコードのみ表示
GROUP BY `user_tbl`.id
HAVING COUNT( `user_tbl`.id ) = 2
-----------------------------------------------------------------------------------------
結果
id name address
1 山田 東京都
12 佐藤 大阪府


これだと、会員・質問・選択肢を自由に追加・削除しても、情報の整合性がとれる。
会員の自由項目情報を扱ったりするときに便利。
(もちろん削除時、変更時にはそれに対応する各々のテーブルデータを書き換える必要はあるが・・・)


※追記
「LEFT JOIN」だと条件が1つのときにuser_tbl他のレコードも一緒にくっついてきてしまってまずいので「INNER JOIN」が正解。

まあ、択一選択にしたければ、最初からラジオボタンかセレクトボックスでフォームパーツを作ればいいのだが、場合によってはチェックボックスで択一選択のフォームを作りたいこともある。(多分そうそう機会はないと思うが)

例えばパラメタの都合上、各inputのname属性の値を別々にしたいときとか。
ラジオボタンでも、name属性の値が違えば、どちらもチェックできてしまう。

以下のようなチェックボックスのフォームが合った場合、

----------------------------------------------------------------------------------
<div class='parts_list'>
  <table>
   <tr>
    <td>
     <input type='checkbox' name='check_A' value='1'> 選択肢A
    </td>
   </tr>
  </table>
  <table>
   <tr>
    <td>
     <input type='checkbox' name='check_B' value='1'> 選択肢B
    </td>
   </tr>
  </table>
  <table>
   <tr>
    <td>
     <input type='checkbox' name='check_C' value='1> 選択肢C
    </td>
   </tr>
  </table>
</div>
----------------------------------------------------------------------------------

これだと、そのままでは全てのチェックボックスにチェックを入れることができてしまうので、これをjQeryで制御する。
簡単にいえば、チェックされた以外のチェックボックスのチェックを外す。

以下が改良版。

----------------------------------------------------------------------------------
<script type="text/javascript" src='jquery.js'></script>

<script type="text/javascript">
 $(function(){

  $("div.parts_list table").each(
   $("input:checkbox",this).click(
    // チェックボックスをチェックした時のイベント開始
    function(){
     // 親要素(table)のセレクタ取得
     var parent = $(this).closest("table");
     // これより前のtableに対する処理
     parent.prevAll().each(
      function(){
       // チェックを外す
       $("input:checkbox",this).attr("checked",false);
      }
     );
     // これより後ろのtableに対する処理
     parent.nextAll().each(
      function(){
       // チェックを外す
       $("input:checkbox",this).attr("checked",false);
      }
     );
     // チェックボックスをチェックした時のイベント終了
    }
   ) // click閉じ
  ) // each閉じ

 )); // function閉じ
</script>


<div class='parts_list'>
  <table>
   <tr>
    <td>
     <input type='checkbox' name='check_A' value='1'> 選択肢A
    </td>
   </tr>
  </table>
  <table>
   <tr>
    <td>
     <input type='checkbox' name='check_B' value='1'> 選択肢B
    </td>
   </tr>
  </table>
  <table>
   <tr>
    <td>
     <input type='checkbox' name='check_C' value='1> 選択肢C
    </td>
   </tr>
  </table>
</div>
----------------------------------------------------------------------------------


以上。

jqueryは便利だなーやっぱり。

スクリプト側の配列をテンプレートに反映させるとき、すべての値をそのまま出力するならいいのだが、値が1のときは「checked」、0の時は空白文字列を出力したい時などがある。

例えば、以下のようなテンプレートがあったとき。

■test.tpl
--------------------------------------------------------------------------------------

<TMPL_IF NAME="users">
 <ul>
  <TMPL_LOOP NAME="users">
   <li>
    名前:<TMPL_VAR NAME="user_name">
   </li>
   <li>
    <input type='checkbox' name='pub_name_on' value='1' <TMPL_VAR NAME="pub_name"> /> 名前を公開する
   </li>
  </TMPL_LOOP>
 </ul>
<TMPL_ELSE>
 <p>要素がありません</p>
</TMPL_IF>

--------------------------------------------------------------------------------------


スクリプト側をオーソドックスに書くとこんな感じ

■test.cgi
--------------------------------------------------------------------------------------

use strict;
use HTML::Template;

# オブジェクト生成
my $tpl = HTML::Template->new( filename => 'test.tpl');

#ユーザ情報を2次元配列にセット
my @users = (
 ['山田 太郎', 0], # 「0」は名前非公開
 ['鈴木 一郎', 1] # 「1」は名前公開
);

#ループ格納用配列作成
my @lood_data = ();

while (@users) {

 # 各行のハッシュ作成
 my %row_data = ();

 # 配列データを格納
 $row_data{'user_name'} = $$_[0];
 $row_data{'pub_name'} = $$_[1];

 # $row_data{'pub_name'} が1だったら「checked」に変更
 $row_data{'pub_name'} = $row_data{'pub_name'} == 1 ? 'checked' : '';

 # リファレンスでループ用配列に追加
 push(@loop_data, \%row_data);

}

# paramメソッド実行
$tpl->param('users' => \@loop_data);

#出力
$tpl->output(print_to => \*STDOUT);

--------------------------------------------------------------------------------------


しかしこれだと、各値をいちいちハッシュにしたり面倒くさいので、下記のようにmapを用いてさらにその中で条件分岐も済ませると楽ちん。

■test2.cgi
--------------------------------------------------------------------------------------

use strict;
use HTML::Template;

# オブジェクト生成
my $tpl = HTML::Template->new( filename => 'test.tpl');

#ユーザ情報を2次元配列にセット
my @users = (
['山田 太郎', 0], # 「0」は名前非公開
['鈴木 一郎', 1] # 「1」は名前公開
);

# mapを使ってパラメータを一気に作成
my %params = ('users' => [
 map { +{
  user_name => $$_[0],
  pub_name => $$_[1] == 1 ? 'checked' : '' # ←ここで条件分岐
 }} @users
]);


# paramメソッド実行
$tpl->param(%params);

#出力
$tpl->output(print_to => \*STDOUT);

--------------------------------------------------------------------------------------

こんなかんじです。
前回に引き続きphpBB(phpBB2)ネタ。

当然ながら日本語でphpBBを運用する場合は、普通、MySQLのマルチバイト文字セットはEUCかUTF8になっている。

しかし、デフォルトのまだと、システムから送られてくるメールの、DBのマルチバイトを参照した部分の文字列が化ける。

以下対策

・language/language_japanese/email の中のファイル全てを、MySQLの文字コードに合わせてエンコードを修正する。
(SJISなどでアップされていたらEUCかUTF8で保存して上書き)

・各ファイルの「Charset: iso-2022-jp」と書かれているところを、全て
「Charset: EUC-JP」 か、
「Charset: UTF8」
に書き換える。

以上
久々の投稿。しかもネタが今更感のあるこれ。
でも今までphpBBを使ったことが無かったので、まあまあ勉強になった。

伝統的なスパム対策は下記らしい。
http://support.hiikun.net/bbs/topic-299.html

ちなみにmodの使い方などはこちら。
http://all.netgamers.jp/adcat5.html

でも、今時のphpBBスパムシステムは、画像認証やMDハッシュを突破したりしてかなり賢い。
上記の対策をしても相変わらず1日100件くらいは登録スパムがやってくる。

まあ、ユーザ登録されるだけならまだいいが、トピックを荒らされるのだけは困るので、
以下の対策を実施。
http://garnote.com/2010/01/spam.html

これはかなり有効だったようで、スパム投稿がピタリと止まった。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。

最近のコメント

ウェブページ

Powered by Movable Type 4.22-ja