月別アーカイブ: 2011年11月

Postfix のインストール (pkgsrcで)

以前 Atom で組んだ NetBSD サーバだが、相変わらず調子が悪く、不定期に再起動が発生している。

ネットワークとかディスクの負荷とかと関係があるかと思って、ルータとして動かすのをやめてみたり、NFSサーバをやめてみたり、各種サーバプロセスを止めてみたりしたものの、改善の様子はない。

結局、別のサーバに完全に移行することにした。

今はメールサーバも外部サーバで運用しているのが、自宅サーバの cron メールを外部のメールサーバに転送するために、当該サーバではまだ Postfix が動いている。そして SASL が必要なために NetBSD 付属のではなく、pkgsrc でインストールしたものを使用している。

そこで移行先のサーバで pkgsrc で Postfix をインストールしたのだが…。

はて? pkgsrc でインスト-ルした方の Postfix を使うにはどうするんだっけ?

元のサーバの /etc/rc.conf を見ても何も書いてない。/etc/mailer.conf もオリジナルのままだ。でも、 ps で確認すると /usr/pkg/sbin/postfix が動いている…。

正解は /etc/rc.conf.d/postfix だった。しかも、以前インストールした時のメモに書いてあるし。

悔しいのでもう一度書いておく。

$ pkg_info -D postfix
Information for postfix-2.8.3nb1:
Install notice:
(略)
===========================================================================
$NetBSD: MESSAGE.NetBSD,v 1.5 2010/03/02 08:07:36 martti Exp $
The existing /etc/rc.d/postfix can be forced to start /usr/pkg/sbin/postfix
instead of /usr/sbin/postfix, by adding the following lines to
/etc/rc.conf.d/postfix:
postfix_command='/usr/pkg/sbin/postfix'
required_files='/usr/pkg/etc/postfix/main.cf'
start_cmd='/usr/pkg/sbin/postfix start'
stop_cmd='/usr/pkg/sbin/postfix stop'
reload_cmd='/usr/pkg/sbin/postfix reload'
postconf='/usr/pkg/sbin/postconf'
Please note that /etc/rc.conf.d/postfix does not exist by default so
you need to create that file if you need to override the default settings.
Remember to modify /etc/mailer.conf to use /usr/pkg/sbin/sendmail instead
of /usr/libexec/postfix/sendmail.
===========================================================================

Zend Framework と Smarty (2)

Zend Framework の view エンジンとして Smarty を使用するやり方は、ネット上にいくらでも紹介されているし、このブログでも先日ネタにした (Zend Framework と Smarty)。

しかし、Bootstrap で Smarty を組み込むと、エラーなどで例外が発生した場合にも Smarty を使用してレンダリングすることになる。

標準の (Zend_Tool で作成した場合だったかな?) ErrorController と view スクリプト error.phtml の組み合わせは、view エンジンが Smarty に置き換えてしまっているので、そのままでは動作しない。そこで、例えば、view スクリプト error.phtml を Smarty 用に書き換えた error.tpl のようなものを自分で用意してやらなければならない。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Zend Framework Default Application</title>
</head>
<body>
<h1>An error occurred</h1>
<h2><?php echo $this->message ?></h2>
<?php if ('development' == APPLICATION_ENV): ?>
<h3>Exception information:</h3>
<p>
<b>Message:</b> <?php echo $this->exception->getMessage() ?>
</p>
<h3>Stack trace:</h3>
<pre><?php echo $this->exception->getTraceAsString() ?>
</pre>
<h3>Request Parameters:</h3>
<pre><?php echo var_export($this->request->getParams(), true) ?>
</pre>
<?php endif ?>
</body>
</html>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Zend Framework Default Application</title>
</head>
<body>
<h1>An error occurred</h1>
<h2>{$message}</h2>
<!-- ?php if ('development' == APPLICATION_ENV): ? -->
<h3>Exception information:</h3>
<p>
<b>Message:</b> {$exception->getMessage()}
</p>
<h3>Stack trace:</h3>
<pre>{$exception->getTraceAsString()}
</pre>
<h3>Request Parameters:</h3>
<pre>
{foreach from=$request->getParams() key=k item=v}
'{$k}' => '{$v}'
{/foreach}
</pre>
<!-- ?php endif ? -->
<div>smarty</div>
</body>
</html>

だが、エラーをハンドリングする場合は Smarty ではなく、Zend_View の方でレンダリングして欲しいと思うケースだってあるだろう。

ここでは、ErrorController の中で View エンジンを Zend_View に設定しなおす方法について紹介する。

結論を先に言うと、下のようなコードを追加すれば良い。

<?php
class ErrorController extends Zend_Controller_Action
{
public function errorAction()
{
if (null !== $this->view and get_class($this->view) != 'Zend_View') {
$view = new Zend_View();
$this->view = null;
$viewRenderer = $this->getHelper('ViewRenderer');
$viewRenderer->setView($view)
->setViewSuffix('phtml')
->initView();
}
$errors = $this->_getParam('error_handler');
switch ($errors->type) {
case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER:
case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ACTION:
// 404 error -- controller or action not found
$this->getResponse()->setHttpResponseCode(404);
$this->view->message = 'Page not found';
break;
default:
// application error
$this->getResponse()->setHttpResponseCode(500);
$this->view->message = 'Application error';
break;
}
$this->view->exception = $errors->exception;
$this->view->request   = $errors->request;
}
}

errorAction() が呼び出された時点で、$this->view は Zend_View_Smarty クラスのオブジェクトが設定されている。これを単純に Zend_View に置き換えても動作しない。$this->getHelper(‘ViewRenderer’) ($this->_helper->viewRenderer と結果は同じ) で取得できる ViewRenderer に View をセットし、かつ、View の初期設定を行わなければならない。

初期設定のための情報は ViewRenderer 内にあって、initView() ($viewRenderer->initView()) を実行すると ViewRenderer に設定された View に対する初期化が行われる。また、アクションコントローラの view ($this->view) が null だと、下のようにアクションコントローラの view に設定してくれる。

// Register view with action controller (unless already registered)
if ((null !== $this->_actionController) && (null === $this->_actionController->view)) {
$this->_actionController->view       = $this->view;
$this->_actionController->viewSuffix = $this->_viewSuffix;
}

今回調べていて感じたのだが、Zend Framework は複数の view エンジンを動的に切り替えて使うようには設計されていないようだ。

上書きする形で置き換えることはできるが、一旦設定されてしまったあとの内部の状態をキャンセルしたり、元に戻したりするのに洗練された方法が見当たらない。

なので、今回のように、Bootstrap で設定したものをアクションコントローラ内で更に設定し直すよりも、Bootstrap では変更しないでアクションの最後のほうで設定する方針としたほうが良いかもしれない。

iframe の高さを中身に合わせる

html の iframe 要素の高さを iframe の中身の高さに一致させ、iframe のスクロールバーを出さなくても全部表示されるようにする、という話。

javascript を使えば出来るらしい。

例えば、
jquery-iframe-auto-height
というのがあって、JQuery のプラグインになっている。

<script type="text/javascript" src="jquery-1.7.min.js"></script>
<script type="text/javascript" src="jquery.iframe-auto-height.plugin.1.5.0.min.js"></script>
$('iframe').iframeAutoHeight();

やってみると、IE8では動かない。いや、ヘッダの中で記述すると最初の読み込み時には動作しなくて、リロードしたときに動作する。そしてボディの iframe の後に記述すると最初の読み込み時から動作する。Firefox7 はどっちに書いても動作する。

<html>
<head>
(略)
<script>
$(document).ready(function () {
$('iframe').iframeAutoHeight();
});
</script>
</head>
<body>
(略)
<iframe src="my_iframe.html" scrolling="no" frameborder="0"></iframe>
<script>
$('iframe').iframeAutoHeight();
</script>
(略)

iframe の中身の読み込みのタイミングがブラウザ間で異なるのに起因するのかな、と思うのだけれど、イベントとして確実に捕まえるにはどうしたらよいかはわからない。

IE8の互換モード

職場のイントラネットのページレイアウトをCSSで作っていたら、どうも思ったように動作しない。

職場のページなので、ユーザは職場の人。うちの職場もしばらく前にようやく IE6 から IE8 に切り替わった(OS はまだ Windows XP だけれども)ので、CSS によるレイアウトがずいぶん楽になった。

ところが、Firefox 7 の表示結果と若干異なる。最初は 8 とはいえ、所詮 IE だから仕方がないか、とも思っていたのだが、:before { content: “text”; } のようなものも動作しない。しかも、ページによっては正常に動作するという不可解な挙動がみられた。

どういうことかというと、IE8 には IE7 などに対応する「互換モード」というのがあるのだが、イントラネットのページでは、「互換モード」がデフォルトになっているのだ(ブラウザの設定で変更することはできる)。

つまり、html ページの置かれている場所がイントラネットとみなされるかどうか、で挙動が変わってしまう、ということだ。

旧来のページを継続して使う、というケースではこのふるまいは確かにありがたいが、新規にページを作る際には邪魔な設定だ。

ユーザは自分だけではないので、ブラウザの方の設定を変えるわけにもいかない。どうすればいいのか調べてみたところ、
HTTP ヘッダか meta タグで、”X-UA-Compatible” に “IE=egde” を指定すれば良いということだった。HTTP ヘッダによる方法だとサイト全体、meta タグによる方法だとページ単位での
指定に向いている。

今回は、確かに古いページも多いので、ページごとに指定する方法を選ぶことにした。

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
(略)
参考

Zend Framework で大きいファイルをダウンロード

表題の通り、Zend Framework 上で大きいファイルをダウンロードするページを作成する手順について考えてみた。

ここでいう「大きいファイル」とは、PHP の出力バッファのサイズを超えるもので、そういう大きいデータを単純に出力しようとすると途中までしか出力されない。

生の PHP だと、そういう場合には出力バッファを切るとか、適当に制御するとかすれば良いのだが、Zend Framework 上で、となると、当然ながら Zend Framework の作法に則った操作が必要となる。

調べてみると、大きく2通りの方針がありそうだ。

一つは、そういう出力を行う Zend Framework に対応したクラスを作成すること。もう一つは、Zend Framework の通常のフローによる出力をキャンセルして、自前でコントロールすることだ。

前者は、アクションコントローラの中でちょっと出力データをダウンロードさせたい、という用途には、やや大げさな気がしたので、比較的簡単そうな後者の方針を取ることにした。

ポイントは、次の通り。

  • 通常のフローによる出力を停止する。
  • ブラウザのダウンロードダイアログが表示されるよう、適切な HTTP レスポンスヘッダを出力する。
  • PHP の出力バッファを停止し、直接データを出力する。
<?php
class PublishController extends Zend_Controller_Action
{
(略)
public function releaseAction()
{
(略)
// Response::sendResponse() が FrontController から
// 呼び出されないようにする
$this->getFrontController()->returnResponse(true);
// Response オブジェクトの取得
$response = $this->getResponse();
// HTTP レスポンスヘッダの出力
$response->clearAllHeaders()
->setHeader('Content-Description', 'File Transfer')
->setHeader('Content-Type', 'application/octet-stream')
->setHeader('Content-Disposition',
sprintf('attachment; filename="%s"', $filename))
->setHeader('Content-Transfer-Encoding', 'binary')
->setHeader('Expires', '0')
->setHeader('Cache-Control', 'must-revalidate, post-check=0, pre-check=0')
->setHeader('Pragma', 'public')
->sendHeaders();
// 以前に何かが出力されようとしていた場合に備えて消しておく
$response->clearBody();
// PHP のバッファをクリアし、バッファを無効にする
while (ob_get_level() > 0) {
ob_end_clean();
}
// データの出力
$h = fopen('ファイル', 'rb');
while (!feof($h)) {
$c = fread($h, 4096);  // 4KB毎に入出力する。最適なサイズは?
$response->setBody($c);
$response->outputBody();
}
fclose($h);
}
}

HTTP レスポンスヘッダは PHP の readfile() のマニュアル を参考にした。

上の例では、Content-Length を出力していないが、外部コマンドの出力などのように事前にサイズが取得できない場合のケースを考えていたため。静的なファイルだったら、ファイルサイズを Content-Length に出力する方が当然良い。

他所でよく見られる例では、アクションコントローラ内で

$this->_helper->viewRenderer->setNoRender();

を実行して、

$this->getFrontController()->returnResponse(true);

を実行していない。
しかし、後者を実行しないと、通常のフローとしてフロントコントローラの dispatch 後に Response::sendResponse() が呼び出されることになる。
ここで HTTP レスポンスヘッダを出力しようとするが、アクションコントローラですでに HTTP レスポンスヘッダを出力しているので、例外が発生し、例外メッセージが(データはすでに出力しているので)データの末尾に付加されてしまう。正しいデータサイズに対応した Content-Length をつけていれば無視されるのかもしれないが、この例のように Content-Length を何らかの理由でつけていないと、データの末尾にゴミが付いてしまうことになる。

続きを読む